Catch the specific error, not "anything that goes wrong"
The single most damaging pattern in AI-generated error handling is the bare except:
try:
do_the_risky_thing()
except:
pass
This catches everything — ValueError, KeyError, KeyboardInterrupt
when you hit Ctrl+C, SystemExit when something tries to shut down the
process, even unrelated bugs the AI didn't anticipate. The script
silently swallows them and keeps going as if nothing happened. Hours
later, you notice the output is wrong, and you have no log line, no
traceback, nothing explaining why.
Cursor reaches for bare except because it feels safe. It is the
opposite of safe. It is the line that hides bugs.
The fix: name the exception you actually expect
except ValueError:
...
Now Python only catches ValueError. Any other exception — a real bug
you didn't see coming — passes through and crashes the script with a
proper traceback. Loud failure beats silent corruption. You want the bug
to crash, because then you can fix it.
You already know the classes to name: the five fingerprints from the
last lesson (ValueError, KeyError, IndexError, TypeError,
AttributeError), plus FileNotFoundError when open(path) can't
find the file. A handful of others show up in specific domains:
JSONDecodeError (bad JSON in response.json()), connection errors
(network down), ZeroDivisionError (a denominator that came in empty).
Stacking excepts: first match wins
A try block can have many except clauses stacked underneath, one
per failure mode:
try:
return int(data[user])
except KeyError:
return None # user isn't in the dict
except ValueError:
return 0 # value was there but wasn't a number
Python checks them top to bottom and runs the first clause whose class matches; the rest are skipped. Two failure modes, two recovery values, zero confusion about which one fired.
Two shortcuts you'll see in real AI code
The tuple form — when two or more exceptions deserve the same recovery, group them:
except (KeyError, ValueError):
return None
That single block matches either class. The parentheses are required. Reach for it only when the recovery is genuinely identical.
The as form — when you want to log or inspect what fired, bind
the exception to a name:
except (KeyError, ValueError) as err:
print(f"failed: {type(err).__name__}")
Inside the block, err is the actual exception instance — same
handler, same fallback, but the log line tells you which failure mode
you hit.
A worked example
The editor on the right runs two completely separate try blocks, each
catching the specific exception that block can produce:
pets = {"dog": "rex"}
try:
print(pets["cat"])
except KeyError:
print("no cat in the dict")
try:
print(pets["dog"] + 7)
except TypeError:
print("can't add a string and a number")
Block one tries to read pets["cat"]. The dict has no cat key, so
Python raises KeyError. The except KeyError block catches it, prints
no cat in the dict, and execution moves on.
Block two tries to add "rex" (a string) to 7 (an int). Python's +
operator doesn't know how to combine those, so it raises TypeError.
The second block catches it and prints can't add a string and a number.
Notice the precision: each except names the specific exception that
matches what the try block can actually raise. Neither uses bare
except. Neither uses except Exception (which is also overly broad).
Where AI specifically gets this wrong
Five patterns to flag in code Cursor writes:
-
except:with no exception named. Always wrong. Replace with the specific exception, or withexcept Exceptionif you genuinely don't know which one — but never bare. -
except Exception: pass. The "I don't want to think about this" move. The script is now blind to every bug in thetryblock. It also welds together failure modes that need different handling: a missing key might mean "user doesn't exist yet" (fine, returnNone), while aTypeErrormeans the code itself is wrong and should crash so you notice. -
Catching the wrong exception entirely. Cursor sometimes writes
except ValueErrorarounddata["user"]["score"], but the lookup raisesKeyError, notValueError. Theexceptdoesn't match, the real exception flies through, and the "handler" never runs. When the handler doesn't fire, the exception name is usually the bug. -
Bundling exceptions that need different handling.
except (KeyError, ValueError, TypeError): passbecause catching more feels safer. Only group exceptions in a tuple when the recovery is genuinely the same. -
Dead
as ebindings. If the variable is bound and never read, it's noise — a smell that the prompt asked for "good error handling" without saying what to do with the error. Either log it, return it, or drop theasclause.
Run the editor. Two try blocks, two specific exceptions, two clean
recoveries.