Does AI-generated code silently swallow errors? 120 measured generations: every flagged case was a false positive or a documented fallback A developer measured 120 AI-generated code samples from Qwen2.5-Coder 1.5B and found that every case flagged as a swallowed error by a naive detector was either a false positive or a documented fallback, concluding that automated verdicts on whether a silent return is a legitimate contract or a bug-hiding swallow cannot be reliably made. The experiment, run locally via Ollama on CPU, hand-adjudicated all 120 labels and published them as gt.csv, arguing that the human judgment layer is the part that resists mechanical reproduction. The work responds to a 2026 preprint (AIRA) reporting 1.80× more high-severity static-analysis findings in AI-authored code, noting the paper itself does not claim the verdict can be automated. If you gate AI-generated code with linters or a CI rule that hunts for swallowed errors, this experiment suggests the part you actually care about — "is this return None a contract or a cover-up?" — is exactly the part the rule cannot decide. I started this project convinced that small LLMs routinely swallow failures — catch an error, return an empty value, pretend nothing happened — and that I'd measure the contamination rate. The hypothesis fell apart in a more interesting way than any clean number would have been. This article is the record of a prior being refuted by measurement, with the collapse documented step by step. Setup: Qwen2.5-Coder 1.5B Apache-2.0 via Ollama, CPU-only, Windows 11. 12 frozen tasks × 10 generations seeds 0–9, temp 0.7 = 120 samples : 100 failure-path functions 50 Python, 50 TypeScript + 20 pure-computation controls. A 7B robustness footnote adds 30 more 150 generations total, all frozen unfiltered in the repo . Classification: Python by AST, TypeScript by regex; all 120 labels eyeballed, every swallow candidate and boundary case hand-adjudicated against docstrings, comments, and the function's contract, published as gt.csv . catch in a try/except and structurally never looked at if -guard default returns, which is where the suspicious shapes actually lived see finding 3 . raise explicitly via if/else loud , 4 return None behind an return None shows up as a documented, role-appropriate contract parse int : None if it cannot be converted fetch json : 404, 500 — every non-200 collapses into the same ⚠ Reproducibility scope: generation is nondeterministic and ran once; all 150 generations are frozen unfiltered in the repo. What you reproduce is the deterministic analysis layer — same 120 files in, same distributions and candidate counts out. The final legitimate-vs-swallow labels are human adjudication , published transparently in gt.csv — and the fact that you can't regenerate that layer mechanically is itself the thesis of this article. AI-generated code quality has a well-cited data point now: AIRA arXiv:2604.17587 https://arxiv.org/abs/2604.17587 , preprint, 2026 ran a deterministic, parser-backed static analyzer over 955 AI-authored and 955 human-authored code samples and reported 1.80× more high-severity findings in AI-authored code 0.435 vs 0.242 per sample , with Broad Exception Suppression C03 — the "swallowed error" family — as the most frequent check 263 vs 185 in Study 3 . That number gets quoted as "AI code swallows errors." But read the paper closely and it says something more careful, twice: So the open question isn't "can we detect suppression patterns?" yes, deterministically . It's the step everyone skips: can the verdict — legitimate fallback or bug-hiding swallow — be automated too? I couldn't find a published measurement of that gap pointers welcome . This article measures it with 120 locally generated functions, a naive detector, and every candidate opened by hand. Scope declaration up front: this is neither "AI is dangerous" nor "static analysis is useless." It's a measurement of where the machine's jurisdiction ends. Three things that look identical in grep output and must not be conflated: 0 / / "" instead of raising. A The whole article is about the gap between the first item and the other two. Classification-wise this sits in CWE-703 Improper Check or Handling of Exceptional Conditions , with CWE-1069 empty exception block and CWE-390 nearby; the exception-antipattern literature de Pádua & Shang, arXiv:1704.00778 https://arxiv.org/abs/1704.00778 , Java/C maps the same territory. Checked against current docs, not from memory: S110 try-except-pass and S112 exist but are select / extend-select . And a except Exception: return sails through anyway. no-empty only fires on empty blocks, so catch e { return null; } is invisible — it's not empty. So I wrote a Semgrep rule that goes after the meaning-shaped pattern — the naive "before" version, verbatim from the repo: rules: - id: py-swallow-return-default languages: python severity: ERROR message: "Exception caught and a default value returned without logging/re-raise silent fallback ." patterns: - pattern: | try: ... except $E: return $R - metavariable-pattern: metavariable: $R patterns: - pattern-either: - pattern: None - pattern: " " - pattern: "{}" - pattern: "0" - pattern: "False" - pattern: '""' - id: py-except-pass languages: python severity: ERROR message: "Exception swallowed with pass silent failure ." patterns: - pattern: | try: ... except $E: pass The TypeScript rules are the same shape — catch { return null } , empty catch — and ship in the repo. My hypothesis at this point: "run this over N generations, get a contamination rate." Here is where the measurement starts disagreeing with me. tasks/tasks.json before generation. Per language: 5 failure-path I/O tasks config loading, HTTP fetch, env vars, numeric parsing, file head + 1 pure-computation control mean/sum — no try needed . Prompts are neutral: no "handle errors" nudging. qwen2.5-coder:1.5b Apache-2.0 , temperature 0.7, top k=40 / top p=0.9 , seeds 0–9 per task, threads fixed at 4. The 7B model appears only in the robustness footnote N=3 per task . ast a real parse ; TypeScript via regex not a full parser . Cross-language | Component | Choice | |---|---| | Inference | Ollama, CPU Windows 11 | | Model main | Qwen2.5-Coder 1.5B, default GGUF quantization Q4 K M-class | | Model footnote | Qwen2.5-Coder 7B | | Detector | Semgrep 1.168.0 CE + custom rules above | | Analysis | Python 3.12, ruff 0.15.12 for the lint baseline | Measured: 2026-07 corpus generated 2026-07-01 . Full per-run metadata in the repo's PROVENANCE file. How the 50 failure-path generations per language handled failure: | Failure-path tasks n=50 per language | Python | TypeScript | |---|---|---| | try/except + log or re-raise proper | 9 | 33 66% | | try/except returning a default swallow candidate | 2 | 0 | | no try/except at all | 39 78% | 17 34% | Reading "no try/except = no handling" would be wrong. AST-splitting Python's 39: raise ValueError ... — that's failing TypeScript's 66% try/catch majority mostly did console.error ... + throw — textbook handling, no swallowing. The language difference Python avoids try/except, TS writes it is an observation, not a finding : prompt phrasing, language idiom async/await + try/catch is TS boilerplate , and the AST-vs-regex classifier asymmetry all confound it. The spine of this article is the counterexample below, not this table. So the original hypothesis — "AI swallows failures at some rate N% I can report" — collapsed in the first table: in this sample, swallowing wasn't the dominant behavior at all. The naive Semgrep rules flagged 4 of 120 2 Python, 2 TypeScript . Opening all four by hand: TypeScript, 2 hits ts load config = false positives. The function under test was exemplary code condensed and annotated from the corpus : async function loadConfig path: string : Promise