Skip to content

fix(parsers/python): guard extraction so one bad file can't abort the whole repo parse#136

Merged
gadievron merged 1 commit into
masterfrom
fix/python-parser-pathological-file-skip
Jul 9, 2026
Merged

fix(parsers/python): guard extraction so one bad file can't abort the whole repo parse#136
gadievron merged 1 commit into
masterfrom
fix/python-parser-pathological-file-skip

Conversation

@gadievron

Copy link
Copy Markdown
Collaborator

What

Add a per-file guard so a single pathological Python file cannot abort the whole repo's extraction (mirrors the existing Zig/Go parsers).

Root cause

FunctionExtractor.process_file guarded only ast.parse (except SyntaxError); the extraction body and both caller loops (extract_from_scan, extract_all) were unguarded. A single file raising a non-SyntaxError — a RecursionError from a deeply-nested source, or any error in the tree walk — propagated out of the file loop and aborted the entire parse, losing all units collected so far.

How

  • Add _process_file_guarded (try/except Exception -> log a WARNING to stderr + files_with_errors += 1) and route all three process_file call sites (extract_from_scan + both extract_all branches) through it. Mirrors parsers/zig/function_extractor.py:57-77.
  • Move files_processed += 1 to the end of process_file, so a file that crashes mid-extraction is counted once as an error and never as processed (no double-count).
  • except Exception (not BaseException) so KeyboardInterrupt/SystemExit still propagate; the failure is logged, never silently dropped.

Regression test

tests/parsers/python/test_function_extractor_robustness.py drives the real entry points (extract_from_scan = the production path used by parse_repository, and extract_all), not hand-built dicts. It covers a parse-time RecursionError, a post-parse extraction error (via monkeypatched process_function), and stats correctness.

RED on origin/master (98f5504):

$ python -m pytest tests/parsers/python/test_function_extractor_robustness.py -q
4 failed

GREEN on this branch:

$ python -m pytest tests/parsers/python/test_function_extractor_robustness.py -q
4 passed

Verification

Byte-identical unit-id set + statistics on click/flask/httpie/rich via both extract_from_scan and extract_all (no change on valid code); recovers every real unit when one file raises at parse OR post-parse. Python parser test suite unchanged vs base.

Compatibility / scope

Author notes

  • Implements: _process_file_guarded + the three re-routed call sites + the files_processed move.
  • Input previously handled wrong: a repo containing one deeply-nested file (RecursionError in ast.parse) returned zero units for the entire repo; it now skips that file and returns the rest.
  • Likely reviewer pushback: "a broad except swallows real errors" — it catches Exception (not BaseException), logs each failure to stderr with the file + exception type, and counts it in files_with_errors, matching the Zig/Go guard and the file's existing stderr-print convention.

🤖 Generated with Claude Code

… whole repo parse

FunctionExtractor.process_file guarded only ast.parse (except SyntaxError); the
extraction body and both caller loops (extract_from_scan, extract_all) were
unguarded, so a single pathological file raising a non-SyntaxError -- e.g. a
RecursionError from a deeply-nested source, or any error in the tree walk --
propagated out of the file loop and aborted the entire parse, zeroing ALL units.

- Add a per-file guard _process_file_guarded (try/except Exception -> log a WARNING
  to stderr + files_with_errors += 1) at both loops, mirroring the Zig/Go per-file
  guards (parsers/zig/function_extractor.py:57-77), the only currently-immune parsers.
- Move files_processed += 1 to the end of process_file so a file that crashes
  mid-extraction is counted once as an error and never as processed (no double-count).
- except Exception (not BaseException) so KeyboardInterrupt/SystemExit still propagate;
  the failure is logged, never silently swallowed.

Verified against origin/master (98f5504): byte-identical unit-id set + stats on
click/flask/httpie/rich via both extract_from_scan and extract_all (no change on
valid code); recovers every real unit when one file raises at parse OR post-parse;
python parser test suite unchanged vs base.

Regression test (fails on master, passes here):
  $ python -m pytest tests/parsers/python/test_function_extractor_robustness.py -q
  4 passed

Does NOT change valid-file extraction output, the func_id scheme, or any other parser.
The same latent pattern in the JS/C/Ruby/PHP extractors is a tracked follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gadievron
gadievron merged commit c81c0e1 into master Jul 9, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant