Bug
py_eval_expr_type in py_lsp.c enters unbounded recursion on certain Python expression patterns, hanging the indexer at 100% CPU indefinitely. The process never terminates and must be killed manually.
Root Cause
Commit 9f981a1 ("Bound LSP resolve recursion depth") added depth guards to three LSP resolvers to prevent exactly this class of bug:
| Language |
File |
Guard |
| Java |
java_lsp.c |
walk_depth cap 512 |
| C/C++ |
c_lsp.c |
walk_depth cap 512 |
| TypeScript |
ts_lsp.c |
member_depth cap 64 |
| Python |
py_lsp.c |
— none — |
Python was missed. The commit message itself notes: "The expression-eval guards (eval_depth) did not cover these walks."
Stack Trace (from sample)
The worker thread spins in a self-referential call chain with no depth cap:
extract_worker
→ cbm_extract_file
→ cbm_run_py_lsp
→ py_lsp_process_file
→ py_resolve_calls_in
→ py_process_statement
→ py_eval_expr_type ← recurses here
→ py_eval_expr_type
→ py_eval_expr_type
→ py_eval_expr_type ← 10+ levels, never terminates
The main thread is blocked on _pthread_join waiting for this worker. Memory grows to ~8 GB from the deep recursion stack. No log output after the file starts extracting — it just burns CPU forever.
Trigger
A single ~6,000-line Python file with complex runtime code (PyTorch tensors, MediaPipe, GStreamer WebRTC, Modal decorators, deeply chained method calls). The file is valid, production Python. I'm not able to share the source, but the pattern that triggers it involves deeply nested method-call chains on dynamically-typed objects where the type resolver can't find a base case.
Reproduction
- Index a repo containing a large Python file (~6K lines) with complex expression nesting
- The parallel extraction phase reaches the file and hangs
sample <pid> shows the recursive py_eval_expr_type chain above
- Process must be killed manually
Environment
- Version: 0.8.1 (latest, darwin/arm64)
- OS: macOS 26.1 (Apple Silicon)
- Memory budget: 4 GB (default), 8 GB RAM system
- Workers: 8
Suggested Fix
Add an eval_depth cap to py_eval_expr_type — same pattern as the walk_depth cap 512 added for Java/C in 9f981a1. Past the cap, the subtree resolves as unknown (graceful degradation). This is consistent with how the other resolvers were fixed.
A regression test (similar to test_stack_overflow.c) with a synthetic deeply-nested Python expression fixture would prevent regressions.
Workaround
Excluding the file via .cbmignore allows the rest of the repo (316 other files) to index cleanly with 6,067 nodes and 21,651 edges.
Bug
py_eval_expr_typeinpy_lsp.centers unbounded recursion on certain Python expression patterns, hanging the indexer at 100% CPU indefinitely. The process never terminates and must be killed manually.Root Cause
Commit 9f981a1 ("Bound LSP resolve recursion depth") added depth guards to three LSP resolvers to prevent exactly this class of bug:
java_lsp.cwalk_depthcap 512c_lsp.cwalk_depthcap 512ts_lsp.cmember_depthcap 64py_lsp.cPython was missed. The commit message itself notes: "The expression-eval guards (eval_depth) did not cover these walks."
Stack Trace (from
sample)The worker thread spins in a self-referential call chain with no depth cap:
The main thread is blocked on
_pthread_joinwaiting for this worker. Memory grows to ~8 GB from the deep recursion stack. No log output after the file starts extracting — it just burns CPU forever.Trigger
A single ~6,000-line Python file with complex runtime code (PyTorch tensors, MediaPipe, GStreamer WebRTC, Modal decorators, deeply chained method calls). The file is valid, production Python. I'm not able to share the source, but the pattern that triggers it involves deeply nested method-call chains on dynamically-typed objects where the type resolver can't find a base case.
Reproduction
sample <pid>shows the recursivepy_eval_expr_typechain aboveEnvironment
Suggested Fix
Add an
eval_depthcap topy_eval_expr_type— same pattern as thewalk_depthcap 512 added for Java/C in9f981a1. Past the cap, the subtree resolves asunknown(graceful degradation). This is consistent with how the other resolvers were fixed.A regression test (similar to
test_stack_overflow.c) with a synthetic deeply-nested Python expression fixture would prevent regressions.Workaround
Excluding the file via
.cbmignoreallows the rest of the repo (316 other files) to index cleanly with 6,067 nodes and 21,651 edges.