Skip to content

fix(selfhost): stop retrying a CLI provider after it times out once - #3987

Merged
JSONbored merged 1 commit into
mainfrom
fix/selfhost-cli-timeout-retry-cap
Jul 7, 2026
Merged

fix(selfhost): stop retrying a CLI provider after it times out once#3987
JSONbored merged 1 commit into
mainfrom
fix/selfhost-cli-timeout-retry-cap

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Scope

Validation

  • npm run typecheck
  • npx vitest run test/unit/ai-review.test.ts test/unit/ai-review-advisory.test.ts -- 203/203 pass, including 2 new tests proving the timeout short-circuit and that transient errors still get full retries.
  • 100% branch coverage on the changed lines (verified via lcov; no gaps within the diff's own line ranges).

Safety

  • No secrets/private data.
  • No auth/API/UI changes.

runWorkersOpinion/runDualAiTieBreakJudgeCall retried every model up to
3 times regardless of failure type, including subscription_cli_timeout
-- a non-transient signal that the same model will time out again on
the same diff. Short-circuit remaining retries of THAT model on a
timeout; the fallback model still gets its own full retry budget.

Closes #3927
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@JSONbored JSONbored self-assigned this Jul 7, 2026
@JSONbored
JSONbored merged commit 38aad56 into main Jul 7, 2026
7 checks passed
@JSONbored
JSONbored deleted the fix/selfhost-cli-timeout-retry-cap branch July 7, 2026 09:00
@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 7, 2026
@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.70%. Comparing base (92a154b) to head (7400ce5).
⚠️ Report is 4 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3987   +/-   ##
=======================================
  Coverage   93.70%   93.70%           
=======================================
  Files         373      373           
  Lines       35076    35079    +3     
  Branches    12863    12866    +3     
=======================================
+ Hits        32868    32871    +3     
  Misses       1584     1584           
  Partials      624      624           
Files with missing lines Coverage Δ
src/services/ai-review.ts 96.51% <100.00%> (+0.01%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

JSONbored added a commit that referenced this pull request Jul 28, 2026
…nd two subprocess leaks (#9476, #9479) (#9505)

claude --output-format json buffers its whole response, so ANY run exceeding its effort
timeout has produced zero stdout when the deadline lands -- which trips the first-output
watchdog and throws claude_stalled_no_output rather than subscription_cli_timeout. The
retry-break tested for the latter by strict string equality, so it never matched: every
timed-out review burned all three attempts (3x180s at default effort, 3x600s at the top tier)
before the fallback model was tried at all. With QUEUE_CONCURRENCY defaulting to 8 that parks
the whole queue during a provider slowdown, and the per-provider circuit breaker needs three
FULL-LENGTH failures before it trips. Matched by prefix, since these errors carry a detail
suffix -- the strict equality is the original bug. subscription_cli_timeout was effectively
unreachable for claude-code, making #3987's fix dead code on this deployment.

child.on("error") catches spawn failures only; it never receives stdio stream errors. A CLI
that exits before draining stdin (an unknown flag on an upgraded binary, an auth abort, an OOM
kill) made the ~250KB stdin write fail with EPIPE on an emitter with no error listener, which
Node escalates to an uncaught exception -> exit(1), taking down every in-flight queue job in
the container.

Per-call temp dirs were never removed. Every AI review minted one and, where repo review
instructions are configured, wrote the composed system prompt into it -- so they accumulated on
the container's writable overlay layer until recreation, leaving those instructions on disk
indefinitely. Removed in the same finally that records CLI usage metrics, best-effort so a
cleanup failure can never turn a completed review into a thrown error.

The systemAppend test now reads the appended-prompt file inside the spawn stub rather than
after the call returns, and additionally asserts the directory does not outlive the call --
which is both the new behaviour and a closer match to how the file is actually used.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(selfhost): cap total wall-clock/subprocess spend when a CLI provider times out repeatedly in one review call

1 participant