diff --git a/.github/workflows/qwen-autofix.md b/.github/workflows/qwen-autofix.md index 4b47cc1e2f..03cf1c7aec 100644 --- a/.github/workflows/qwen-autofix.md +++ b/.github/workflows/qwen-autofix.md @@ -124,6 +124,7 @@ task-oriented guides — what a maintainer types and what happens next — see: - [70. review-address · Report dry-run / failure — -c drops any partial multi-byte sequence a byte-level head -c may have split, so the…](#af-070) - [71. review-address · Report dry-run / failure — Bilingual companion. Repo convention is English first, Chinese in a collapsed
.…](#af-071) - [72. review-address · Report dry-run / failure — Flip the status comment out of "working" so a finished round never leaves a live-looking…](#af-072) +- [73. review-address · Report dry-run / failure — Idle (silent-sandbox) timeouts are EXCLUDED from the cumulative timeout cap.…](#af-073) --- @@ -2090,3 +2091,68 @@ publishing" and report a successful round as a failed one. An empty 'stale' (prepare itself crashed) still finalises — that IS this job's round, and it is exactly the case that must not stay "working". ``` + + + +### 73. review-address · Report dry-run / failure — Idle (silent-sandbox) timeouts are EXCLUDED from the cumulative timeout cap. + +In `review-address` · `Report dry-run / failure`. + +```text +TIMEOUT_WINDOW_CAP exists to stop a PR that is too big to finish a +round inside the agent's time budget; its remedy says so ("split or +reduce the PR, or raise the agent time budget AND its step backstop"). +An idle timeout is a different failure entirely: run-agent.mjs's idle +watchdog kills the round after QWEN_IDLE_TIMEOUT_MS (20m) because the +sandbox produced no output at all — the four observed hangs (#8663 x2, +#8761 r3, #8763 r4) each printed their last byte at docker container +entry and then sat silent. Nothing about the PR caused it, and the +breaker's own headline already told the reader that "no budget increase +can cure" it. Counting a failure whose prescribed remedy is +inapplicable is what parked healthy PRs. + +Measured on 2026-08-21, over the preceding 14 days: 119 timeouts, of +which 58 (49%) were idle. 51 windows tripped this cap, every one of +them at exactly N=3. Of the 12 open PRs then carrying +autofix/needs-human, 9 had been stopped here — #8332 at 24 rounds, +#8368 at 28, #8276 at 16, all still producing pushed rounds when they +were parked. With idle rounds counted, the fleet timeout rate was +8.5% per round, so a window accumulated three of them in ~35 rounds by +arithmetic alone, independent of whether the PR was stuck. Excluding +idle drops the rate to 4.3%, which needs ~69 rounds — beyond the +deepest window ever observed (22/100). + +The escape hatch that makes the exclusion safe: an idle round pushes +nothing and matches none of CONSEC_FAIL's streak-reset needles +("Addressed the latest review feedback", "no changes needed", "AutoFix +could not start", "updated a stale base"), so a persistently wedged +sandbox still terminates the PR at CONSECUTIVE_FAILURE_CAP. What no +longer terminates it is idle rounds INTERLEAVED with real progress — +which is the intended change: that PR is not stuck, the runner is. + +Two consequences inside the block. IDLE_N's needle became the full +emitted headline prefix ('AutoFix ran out of time before finishing +(idle-timeout') rather than a bare 'idle-timeout' substring: IDLE_N is +now subtracted from TIMEOUT_N, so it MUST be a subset of it, and a +loose needle could otherwise match provider error text that +API_ERROR_DETAIL puts on the same first line and drive the difference +negative. And the all-idle remedy branch is gone as unreachable: the +guard now fires only when BUDGET_TIMEOUT_N alone reaches the cap, so a +tripped window always holds at least TIMEOUT_WINDOW_CAP genuine budget +timeouts — idle rounds can outnumber budget ones in it, but the budget +remedy applies because those budget timeouts exist, not because they +are the majority. + +Idle rounds stay visible through a job-log ::warning:: rather than a PR +comment — the signal belongs to whoever owns the runners, and infra +noise should not spend a comment on someone's PR. The census and its +warning run outside the cap's terminal guard: the all-idle shape stops +via the consecutive breaker with that breaker's headline, and the +terminal run's log is exactly where the wedged runner must be named. + +The same exclusion applies to the prepare step's PRIOR_TIMEOUTS census +(af-049): its budget warning tells the agent to narrow scope — the +budget remedy again — and an idle round never exhausted any budget, so +it must not steer the narrowing. Idle rounds are excluded there with +the same needle the cap census uses. +``` diff --git a/.github/workflows/qwen-autofix.yml b/.github/workflows/qwen-autofix.yml index 5917c30370..31cbbb6112 100644 --- a/.github/workflows/qwen-autofix.yml +++ b/.github/workflows/qwen-autofix.yml @@ -245,7 +245,9 @@ env: # pushed rounds in between, so the consecutive cap never fired and the PR # kept walking into the same wall; #7846 the same, twice. Counted over the # current counting window (window-scoped like every other census), so a - # re-arm clears it along with the round counter. + # re-arm clears it along with the round counter. Counts BUDGET timeouts + # only: silent-sandbox (idle) timeouts are infra, not PR size, and are + # excluded — see qwen-autofix.md#af-073. TIMEOUT_WINDOW_CAP: '3' # Do not claim more issues when too many existing autofix PRs are still open. MAX_OPEN_AUTOFIX_PRS: '5' @@ -4991,6 +4993,9 @@ jobs: # Time-budget exhaustions SINCE THE LAST SUCCESSFUL ROUND mean # the standard address-everything prompt is not converging at # Full rationale → qwen-autofix.md#af-049 + # Idle (silent-sandbox) timeouts are excluded like in the cap + # census: the narrowing advice targets budget exhaustion, and an + # infra-killed round never had any budget to exhaust (af-073). PRIOR_TIMEOUTS="$(jq -r --arg ab "${AUTOFIX_BOT}" --arg key "${LIVE_REARM_KEY}" ' [ .[] | select((.user.login // "") == $ab) | select((.body // "") | contains("