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:
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:
-
Is this failure expected at this layer? If the database is down, that is not the
get_user_profilefunction's problem to absorb — it is a 500 that the caller and the retry layer should see. -
What should the caller receive instead? A
Nonethat gets passed downstream is worse than a thrown exception, because it moves the bug one level away. - 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
Noneunless the caller already handlesNone.
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 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.
Top comments (0)