fix: give the shadow review its own wall-clock budget #459
No reviewers
Labels
No labels
Agent
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No assignees
1 participant
Notifications
Total time spent: 1 minute 7 seconds
Due date
igor
1 minute 7 seconds
No due date set.
Dependencies
No dependencies set
Reference
joshtronic/igor!459
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/453-review-timeout"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #453
#453 reported
review call failed19x on one PR with no cause. The cause is now in the journal, courtesy of #454 puttinglog()on stderr:124istimeout(1).What's actually wrong
claude_calldefaults to 300s, andreviewer_effortescalates tomaxat rework round 3 — which outgrows it. Measured on igor#455, same PR, consecutive rounds:highmaxSo
maxon a diff near the 400-line scope cap runs ~2.5×highand varies enough to straddle 300s. The escalation fires exactly when a PR is hardest, so the reviewer becomes least able to return a verdict precisely when one is most needed.And the consequence is worse than a slow review. A timeout leaves the head un-recorded, so
do_review_tickre-picks the same PR next tick and every other PR in the fleet queues behind it. On 2026-07-30 that presented as a fleet-wide review outage for ~30 minutes until an attempt got lucky — joshing.you#162 sat unreviewed the whole time.600s is ~3× the observed successful max-effort run, and the same budget the sports digest already uses for its long call. Hardcoded, not an env knob.
Deliberately not changed
The retry count, and any skip-after-N-failures guard. The loop self-healed on retry, so there's no evidence of a permanent block — only of a budget set below the work. A guard would be building for a failure that hasn't happened, and I'd rather find out than pre-empt it.
Tests
bin/test-review-timeout.sh. The behavioural half is the load-bearing one: it drives the realclaude_callwith a stubbed CLI that outlives its budget, and asserts the 7th argument is honoured at all. If it were ignored, the new constant would be decoration and the bug would survive the PR intact.The rest are source assertions, including one that fails if the budget is ever set at or below
claude_call's default — the shape that would look fixed while changing nothing. Mutation-checked both ways: setting it to 300, and dropping the argument at the call site.check-sync clean, shellcheck clean.
🤖 Generated with Claude Code
https://claude.ai/code/session_01QfDyMfn2Go73gw21a92wNB
🤖 Review —
APPROVE(automated)CI for
8c088d2b: successBumps the shadow-review
claude_callto a dedicated 600s budget (up from the 300s default) and addsbin/test-review-timeout.sh. CI green, scope tight (~110 lines, one issue), description matches the diff exactly.What I checked
bin/tick.sh:1091-1108—REVIEW_CALL_TIMEOUT_SECS=600is defined at top level (betweenreviewer_effortandworker_effort), so it's in scope by the timedo_review_tickruns at ~3047. No shadowing, nolocal, no env-override path that could reintroduce 300s.bin/tick.sh:3050— the 7th positional is the only change to the call; args 1–6 are untouched.claude_callwith asleep 30stub CLI and a 2s budget, and asserts both the non-zero return and that it died at ~2s rather than 30s. That's the one assertion that would fail ifclaude_callignored arg 7 — which matters, sincelib/claude.shisn't in this diff and I can't otherwise confirm the parameter exists. Combined with green CI, I'm satisfied it does.BUDGET > DEFAULTandBUDGET >= 400source assertions close the "looks fixed, changes nothing" hole. Good instinct.Non-blocking notes for the record
do_review_tickstill retries (for attempt in 1 2), so a pathological review can now occupy up to 1200s of wall clock inside one tick instead of 600s. If anything upstream (systemdTimeoutStopSec, a tick-level lock/watchdog, timer interval) sits below that, the failure mode changes from "review times out, retried next tick" to "tick killed mid-review", which is strictly worse. Worth a glance at the unit file; nothing in this diff tells me either way.do_review_tickre-picks the same PR forever — is unchanged; 600s just makes it much less likely to trigger. The PR is explicit about deferring the skip-after-N guard and gives a defensible reason (no evidence of a permanent block yet). Fine as a decision, but if #453's symptom recurs, that's the next thing to fix, not another budget bump.[ "$SECS" -le 6 ]against a 2s budget leaves ~4s of slack. Probably fine, but under a loaded runner this is the assertion that will go red first.claude_call "${AGENT_MODEL_REVIEW}:${rev_effort}"...— any reformatting of that line fails the test with a message that reads like a real regression. Acceptable tradeoff for catching a dropped argument, just be aware.Nothing here blocks. Approving.
Independent review by the harness on
claude-opus-5(effort: high). The human reviewer is requested once Igor has reviewed; a human still merges.