Skip to content

fix(index): yield during resolution so the liveness watchdog can't kill a valid large index (#1091)#1105

Merged
colbymchenry merged 1 commit into
mainfrom
fix/watchdog-resolution-yield-1091
Jul 1, 2026
Merged

fix(index): yield during resolution so the liveness watchdog can't kill a valid large index (#1091)#1105
colbymchenry merged 1 commit into
mainfrom
fix/watchdog-resolution-yield-1091

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Problem (#1091)

Indexing a large project gets SIGKILLed partway through with "Main thread unresponsive for ~60s — killing the wedged process (#850)", even though the index is healthy and progressing. Users were forced to set CODEGRAPH_NO_WATCHDOG=1. Reported on both a 22k-file repo (bar at 100% then wedge) and a 134-file repo (88%).

Root cause

The #850 liveness watchdog SIGKILLs a process whose main-thread event loop stalls past its window (60s default). Its heartbeat is a setInterval on that thread. It was wired into index/init in #999 — but unlike the serve daemon (whose heavy work is off-thread in the parse worker / shelled out), reference resolution and callback-edge synthesis run synchronously on that same thread. On a large repo those spans legitimately run for minutes, so the heartbeat can't fire and the watchdog kills valid, in-progress work. The progress bar freezes wherever it last rendered (88% / 100%) when a synchronous span begins.

Fix

Make the long synchronous spans yield cooperatively (src/resolution/cooperative-yield.ts, a budget-gated maybeYield()), so the heartbeat keeps firing during real work while a genuinely wedged span — which never reaches a yield — still trips the watchdog:

  • synthesizeCallbackEdges yields between its ~33 whole-graph passes, and the heavy scanners (closure-collection, event-emitter, JSX-child, object-registry, field-channel) yield within their loops;
  • batched resolution sub-chunks each batch with yields;
  • the deferred chained-call and this-member post-passes yield per ref.

Behaviour-preserving — only timing changes; node/edge counts are identical.

Validation (real watchdog armed at the default 60s)

Test main this branch
Swift compiler (27k files, 441s resolving, ~1.1M edges) KILLED @60s (dies at 644s, in the end-stage post-passes) complete graph — 0 unresolved refs, 99.99% of baseline edges, no kill (×3 runs)
Swift C++ subset (4.9k files, 165s resolving) completed COMPLETED @60s (node/edge parity, 1-edge nondeterminism)
TypeScript compiler KILLED @1–2s COMPLETED

Full test suite green (1890). New regression test __tests__/cooperative-yield.test.ts pins the yielder's budget contract and locks the async structure of the three spans.

Closes #1091.

🤖 Generated with Claude Code

…ll a valid large index (#1091)

The #850 liveness watchdog SIGKILLs a process whose main-thread event loop
stalls past its window (60s default). It was extended to `index`/`init` in
#999, but reference resolution and callback-edge synthesis run synchronously
on that same thread — so on a large repo a legitimate, in-progress index gets
killed, and users had to disable the watchdog entirely (CODEGRAPH_NO_WATCHDOG=1).

Make the long synchronous spans yield cooperatively so the heartbeat keeps
firing during real work, while a genuinely wedged span (which never reaches a
yield) still trips the watchdog:

- synthesizeCallbackEdges yields between its whole-graph passes, and the heavy
  scanners (closure-collection, event-emitter, JSX-child, object-registry,
  field-channel) yield within their loops;
- batched resolution sub-chunks each batch with yields;
- the deferred chained-call and this-member post-passes yield per ref.

Behaviour-preserving — only timing changes; node/edge counts are identical.

Validated end-to-end with the real watchdog armed at the default 60s: the
released build is SIGKILLed partway through indexing the Swift compiler (27k
files, ~1.1M edges) and the TypeScript compiler, while the fixed build indexes
both to completion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@colbymchenry colbymchenry merged commit ed39233 into main Jul 1, 2026
1 check passed
@colbymchenry colbymchenry deleted the fix/watchdog-resolution-yield-1091 branch July 1, 2026 17:13
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.

Issue with codegraph init failing to complete in my project.

1 participant