* fix(ci): stop counting wedged queued runs as in-flight in the shepherd
When GitHub refuses to start a workflow run it still CREATES it: the run sits
`queued` forever with zero jobs and cannot be cancelled or deleted through the
API. On 2026-08-19 an oversized qwen-autofix.yml produced a run like that from
the shepherd's own liveness dispatch, and the watchdog counted it as in-flight
for the next 18 hours:
last scan signal: 2026-08-19T05:01:14Z (1107m ago), in-flight: 1
The age gate said "dispatch a scan", the in-flight gate said "one is already
running", and nothing ever completed the run that would clear it. The loop
stayed dark until a human looked.
Treat a run still `queued` past ZOMBIE_QUEUED_MINUTES (30, overridable via the
QWEN_SHEPHERD_ZOMBIE_QUEUED_MINUTES repository variable) as wedged rather than
live. One `wedged` predicate is defined once and reused by the in-flight count,
the conflict lever's busy-set, and a new census, so the three readers cannot
disagree. Only `queued` runs wedge — a review-address run legitimately runs for
hours — and a missing createdAt reads as brand new, so unknown age never
licenses a duplicate dispatch.
The wedge is now visible instead of silent: a :⚠️: names the count and
the oldest one, the tick heartbeat carries `wedged-queued:`, and the dashboard
carries a banner. Invisibility is what made this expensive — PR-event runs kept
reporting success while every scheduled scan was dead.
Verified against the real run list from the incident: the old predicate returns
in-flight=1 (starved), the new one returns 0 with a census of 2. Behavioral
tests replay both jq programs and the busy-set walk verbatim from the workflow.
* fix(ci): keep shepherd busy-set job-verified and bound wedge re-dispatch (#9518)
* fix(ci): reject degenerate zombie threshold and name the paused liveness gate (#9518)
* fix(ci): name the recorded liveness run in the shepherd wedge remedy (#9518)
---------
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>