# Your Agent Wrapped the Whole Function in try/catch and Called It Error Handling

## The pattern that shows up in every agent PR

You paste a stack trace into your agent. Twenty seconds later it comes back with a diff like this:

```python
def get_user_profile(user_id):
    try:
        row = db.query(SQL, user_id)
        return row_to_profile(row)
    except Exception as e:
        log.error("profile fetch failed", e)
        return None
```

The stack trace stops. The tests pass. The PR description says "added error handling."

Two weeks later a user reports a blank profile page. The logs show one error line from that day, and nothing else. The caller did not check the return value, so `None` got passed downstream, and the failure surfaced two layers away as a render error. The original database exception never made it to anyone on call.

## What the try/catch actually solved

It silenced the stack trace on the developer's screen. It did not solve the failure.

The database still failed. The user still did not get a profile. The on-call engineer still had no alert, because the error was logged once at INFO level and the request returned 200.

Wrapping a call in `try/catch` and returning a sentinel value is not error handling. It is failure containment with the lid left off. The exception still exists, it just stops propagating to the person who can fix it.

## What real error handling has to decide

Every caught exception has to answer three questions, and a blanket `except Exception` answers none of them:

1. **Is this failure expected at this layer?** If the database is down, that is not the `get_user_profile` function's problem to absorb — it is a 500 that the caller and the retry layer should see.
2. **What should the caller receive instead?** A `None` that gets passed downstream is worse than a thrown exception, because it moves the bug one level away.
3. **Who gets paged?** A logged error line with no alert goes nowhere. Either re-raise so the framework logs it at the right level, or send the signal somewhere that will page a human.

A narrow `except` for the specific exception you actually expect (a row-not-found, a validation error), plus a re-raise for everything else, is usually the correct shape. It is also the shape an agent avoids, because it requires knowing which exceptions are expected in your system — something it cannot infer from the stack trace alone.

## The prompt that forces the decision

When you ask the agent to "add error handling," you get the blanket pattern. Instead, ask it:

> For this function, list the specific exceptions that are expected at this layer. For each one, say what the caller should receive and whether a human needs to be alerted. For anything else, re-raise. Do not return `None` unless the caller already handles `None`.

This pushes the agent to reason about the failure modes instead of reaching for the catch-all. It still gets the stack trace to stop — but the re-raised exceptions keep propagating to the layer that knows what to do with them.

## The review smell to look for

When you see a `try/catch` in a PR, the first question is not "does it compile?" It is:

- What exception is this actually catching?
- What does the caller get back instead?
- What happens to the error after the log line?

If the answer to any of those is "nothing," the try/catch is making the system worse, not safer. The original stack trace was a feature — it was the system telling you where the bug was. Swallowing it removes the signal without removing the failure.

## The same instinct shows up on the API side

Agents tend to do the same thing at the HTTP boundary: wrap the handler in a broad exception, return 200 with an empty body, and call it "graceful degradation." The client gets a successful response, the monitoring sees nothing, and the contract drift between what the client expects and what the server returns goes undetected until a user reports it.

The cheaper guard we landed on locally in [Powerduck](https://www.powerduck.com/) is to re-run the spec against the live endpoint before the agent calls a change done — a 200 with the wrong shape fails the check, instead of silently becoming "the API returned successfully." It does not replace the narrow-exception habit; it catches the other half of the same failure mode.

## What to change tomorrow

Next time your agent opens a PR with a new `try/catch`, ask it the three questions above. If it cannot answer them, the catch is too broad. Real error handling is a decision about what failure means at that layer — not a blanket around the whole function.
