AI-Generated Error Handling for Python Automation Scripts
AI loves bare except clauses and swallowed errors. Learn to prompt for precise exception handling, useful failure messages, and clean exits in Python automation.
- #python
- #bash
- #error-handling
- #exceptions
Ask an AI for “robust” Python automation and you’ll often get the most dangerous code it can produce: a try block wrapping the whole script with except Exception: pass. It looks robust. It’s actually a black hole that swallows every failure and reports success regardless. AI is a fast junior engineer, and junior engineers reach for broad exception handling because it makes errors “go away.” The job of error handling is the opposite — to make failures visible and recoverable. Here’s how I prompt for that, and what I review before any of it runs on prod.
Name the failures you actually expect
Good error handling starts by enumerating real failure modes. I prompt: “List the specific exceptions this code can raise — network, filesystem, parsing, auth — and handle each one distinctly.” Naming them forces the AI off the generic catch-all.
For an API-polling script it produced targeted handling:
import httpx
try:
resp = httpx.get(url, timeout=10)
resp.raise_for_status()
data = resp.json()
except httpx.TimeoutException:
log.error("timed out talking to %s", url)
raise
except httpx.HTTPStatusError as e:
log.error("bad status %s from %s", e.response.status_code, url)
raise
except ValueError:
log.error("response was not valid JSON")
raise
Each branch logs something actionable and re-raises. The AI does this well when you list the failures; left to “robust,” it wraps the lot in one bare except. The resilient HTTP in Python guide covers the httpx specifics.
Forbid bare except in the prompt
I literally write: “Do not use bare except: or except Exception except at the top level for last-resort logging.” Even then the top-level handler must log and exit non-zero, never swallow.
def main():
try:
run()
except Exception:
log.exception("unhandled error, aborting")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
log.exception records the full traceback, and the non-zero exit means cron, CI, or a supervisor knows the job failed. The AI’s instinct is except Exception: pass; you have to explicitly demand the log-and-exit version and then confirm it actually exits non-zero. I’ve caught it returning 0 from the handler more than once.
Pro Tip: ask the AI “for each except block, what happens to the caller and the exit code?” A swallowed exception that returns a success exit code is the single worst automation bug, because the orchestrator thinks the job worked. Forcing the AI to trace the exit code surfaces these.
Distinguish retryable from fatal errors
Not every failure should retry. A 503 is transient; a 401 means your token is wrong and retrying is pointless. I prompt the AI to classify errors so retries only apply where they help.
RETRYABLE = {429, 500, 502, 503, 504}
def call_with_retry(client, url, attempts=3):
for attempt in range(1, attempts + 1):
try:
resp = client.get(url, timeout=10)
resp.raise_for_status()
return resp.json()
except httpx.HTTPStatusError as e:
if e.response.status_code not in RETRYABLE or attempt == attempts:
raise
time.sleep(2 ** attempt)
The AI got the structure right but initially retried on all status codes, including 401 — hammering an auth endpoint that will never succeed. I narrowed it to the retryable set. This is the pattern from retry and backoff patterns, and the distinction between retryable and fatal is exactly the judgment a junior misses.
Clean up resources on failure
Error handling and cleanup are the same concern. A failure that leaves a temp file, an open connection, or a lock held is a half-handled error. I ask for context managers everywhere.
from contextlib import contextmanager
@contextmanager
def acquired_lock(path):
fd = open(path, "w")
try:
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
yield
finally:
fcntl.flock(fd, fcntl.LOCK_UN)
fd.close()
The finally guarantees the lock releases even if the body raises. The AI writes clean context managers when prompted, and this dovetails with file locking and signal handling.
Make error messages actionable, not decorative
“An error occurred” helps nobody. I tell the AI: “Every error message must say what failed, what input caused it, and what to do next.” The difference is whether the on-call engineer can act without reading the source.
raise RuntimeError(
f"failed to write manifest to {path!r}: "
f"directory is not writable; check permissions or DISK space"
)
I review these messages for honesty — the AI sometimes asserts a cause it can’t actually know (“check your network connection” for what’s really a permissions error). Misleading error messages waste incident time, so I correct them to state only what the code can verify.
Log failures into your real alerting path
Caught-and-logged is only useful if someone sees the log. For automation that matters, failures should reach the same place incidents do. The monitoring alerts dashboard and incident response dashboard are where these belong, and structured logging makes them parseable — see structured logging in bash and Python.
When I wire up the alerting, the webhook and tokens come from the environment. I never paste a real credential into the AI chat to “test the notification.” It designs the error-handling logic; I supply secrets at runtime, locally.
Conclusion
AI reaches for broad exception handling because, like a fast junior engineer, it wants errors to disappear. Real error handling makes failures loud, classifies retryable from fatal, cleans up resources, writes honest actionable messages, and always exits non-zero on failure. Enumerate the real failures in your prompt, forbid bare excepts, trace every exit code, and review every message for honesty before it runs on prod. The speed is the AI’s; the judgment about what a failure means is yours. More error-handling prompts live in the prompts library and the bash and Python automation category.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.