Repository navigation
CI: retry wedged codex agent legs once; canary stops reporting them as "stalled" - #489
Conversation
…s "stalled" Five Monitor slots in five days (2026-09-07..11) lost one codex agent leg to a wedge. Since PR #471 the leg dies at timeout-minutes, GitHub records that job — and the whole run — as "cancelled", and workflow-canary.yml dropped cancelled runs from its "last scheduled run" lookup. The gap it measured then doubled to ~8h and PagerDuty got two "Monitor: stalled — no run in 5h30m" pushes per wedge, although Monitor was scheduling fine. agent-retry.yml (new): on workflow_run completion of Monitor / Daily Project Assessment / Product Analysis with conclusion failure or cancelled and run_attempt == 1, call rerun-failed-jobs. Only the dead leg and its dependants re-run on a fresh runner; gather's evidence artifact is reused (no second VPN session, no duplicate baseline sample). Cancelled jobs without the timeout annotation (manual / concurrency-queue cancel) are not retried. Verified on a throwaway run: rerun-failed-jobs does include a job killed by timeout-minutes (attempt 2 re-ran it). workflow-canary.yml: any scheduled run now counts for the stall check; a cancelled latest run whose job carries the "exceeded the maximum execution time" annotation is classified failed (conclusion reported as timed_out), other cancels still fall back to the newest non-cancelled run. Table gains an attempt column and a ::notice when the latest run needed a retry, so the wedge rate stays visible. monitor.yml finalize: needs.agent.result == "cancelled" (what a timed-out leg reports) fails the run like "failure". Verified the run conclusion stays "cancelled" regardless, which is why the canary change above is needed rather than optional. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4AMHh8hMfZh53Tnj3TSSn
17eb4ce to
8349819
Compare
…end on main workflow_run only listens to workflows on the default branch, so the retry chain cannot be exercised from a PR branch. This fixture (attempt 1 sleeps past a 1-min timeout, attempt 2 succeeds or also times out depending on input) is dispatched after merge and removed in the follow-up PR. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4AMHh8hMfZh53Tnj3TSSn
|
End-to-end proof plan (owner asked for certainty today).
Results will be posted here. |
|
End-to-end results (all on
One casualty: the first canary test run (34833098162) was meant to be a dry run but sent a real |
…ys live (#490) The temporary _retry-probe.yml from #489 has done its job (see #489 comments): attempt 1 killed by timeout-minutes, Agent Retry fired within 10 s and called rerun-failed-jobs, attempt 2 re-ran only the dead leg, the completion of attempt 2 was ignored by the run_attempt guard, and in timeout-both mode exactly one retry happened (no attempt 3). Canary: `inputs.dry_run == 'true'` compares a boolean input to a string; GitHub coerces both to numbers (1 vs NaN) so it was always false and every dry-run dispatch sent real PagerDuty events — which is how a "Retry Probe (temporary): stalled" warning reached the owner at 10:25Z today (resolved manually). `inputs.dry_run == true` fixes it; verified on branch canary-probe-test run 34833513756's successor: "[dry-run] would trigger" lines, no HTTP calls. Claude-Session: https://claude.ai/code/session_01V4AMHh8hMfZh53Tnj3TSSn Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
What was paging
PagerDuty got repeated
Monitor: stalled — no run in 5h30mpushes between 2026-09-07 and 2026-09-11. Monitor never stopped scheduling. What happened each time:agent (vahter)oragent (coupon)) wedged. The sibling leg in the same run finished in 1–3 min.timeout-minutes: 20. GitHub records a timed-out job ascancelled, and the whole run endscancelled.finalizepassed because it only checked forfailure.workflow-canary.ymlexcluded cancelled runs from its "last scheduled run" lookup, so the gap it measured jumped from ~4h to ~8h and it firedstalledfor the next two polls until the following Monitor slot resolved it.Changes
agent-retry.yml(new) — one automatic re-run on a fresh runnerworkflow_runon completion of Monitor, Daily Project Assessment, Product Analysis (the three codex-action workflows Monitor: 20-min timeout on agent legs + codex transcript artifact #471 listed).failureorcancelledandrun_attempt == 1, so at most one retry per run, ever. Attempt 2's completion re-triggers this workflow and is ignored by the guard.POST …/actions/runs/{id}/rerun-failed-jobs. Only the failed/cancelled jobs and their dependants re-run;gatherand itsmonitor-evidenceartifact are reused, so no second VPN session and no duplicatebaseline.sh appendsample. Falls back to a fullrerunonly if that endpoint refuses.exceeded the maximum execution time. A manual cancel or anaks-vpnconcurrency-queue bump stays cancelled.safety-strategy: drop-sudois one-way per runner (seemonitor.ymlheader), so a second invocation on the same runner dies atensurePasswordlessSudo. A job re-run is a fresh runner.workflow-canary.yml— cancelled runs no longer vanishfailedwith conclusion reported astimed_out. Any other cancelled run is skipped in favour of the newest non-cancelled one, exactly as before.attemptcolumn and a::noticeis logged when the latest run needed attempt 2, so the wedge rate stays visible even though it no longer pages. Needschecks: readfor the annotations call.monitor.ymlfinalizeneeds.agent.result == "cancelled"(what a timed-out matrix leg reports — confirmed in run 34579969780's finalize log) now fails the run likefailure.Verified on a throwaway workflow (branch-only, deleted before this PR)
Job with
timeout-minutes: 1+sleep 300, followed by anif: always()finalize that exits 1 (run 34830783954):timeout-leg→cancelled, finalize →failure, run conclusion →cancelled. So the finalize fix alone never turns a wedge red at run level; the canary change is required, not optional.POST rerun-failed-jobs→ HTTP 201, and attempt 2 containedtimeout-legin progress. Timed-out jobs are included in "failed jobs".actionlintclean on all three files.What this does not do
::noticeline in the canary log is the running tally until someone digs into it.Invalid format 'loki'/ coupon prometheus unreachable), which recovered on its own the next slot. A retry would also cover that class, since gather is a normal failed job.After merge, watch for
Agent Retryruns skipping with "deliberate cancel, not retrying" on runs you cancelled by hand.🤖 Generated with Claude Code
https://claude.ai/code/session_01V4AMHh8hMfZh53Tnj3TSSn