Skip to content

fix(scheduler): stop re-provisioning undispatchable zombie jobs#95

Merged
ephpm-claude[bot] merged 1 commit into
mainfrom
fix-zombie-job-reprovision
Jul 8, 2026
Merged

fix(scheduler): stop re-provisioning undispatchable zombie jobs#95
ephpm-claude[bot] merged 1 commit into
mainfrom
fix-zombie-job-reprovision

Conversation

@ephpm-claude

@ephpm-claude ephpm-claude Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

A workflow run GitHub keeps listing as queued but never dispatches to a runner — classically a run superseded by a newer commit on a workflow without concurrency:cancel-in-progress — made ephemerd re-provision a full runner/VM for it every seenTTL, forever.

Observed live: 3 stale php-sdk macOS jobs (19h old, 8 commits behind HEAD, runner=(never assigned)) each booted a fresh macOS VM every ~10 min indefinitely — ~4GB + boot per cycle for nothing — and inflated the queue so php-sdk looked backed up. The current-HEAD build was green the whole time.

Fix

A per-job provisioning-attempt counter. The seen dedup lets a given job past provisioning only ~once per seenTTL, so:

  • a live job reaches provisioning once (runs to completion, then stops appearing),
  • a zombie reappears each cycle → after maxProvisionAttempts (~50 min of retries) it's skipped with a one-time warning instead of re-provisioning.

The counter is pruned in cleanSeen once the job stops appearing, so a later legitimate rerun of the same job id starts fresh.

Tests

  • TestHandleQueued_ZombieSkip — skip fires past the cap, job is never claimed
  • TestCleanSeen_PrunesAttempts — counter reset when the seen entry expires

Notes

Generalizes the earlier ghost/zombie churn (deleted-branch runs) into a single guard. Complements a php-sdk-side fix (add concurrency:cancel-in-progress so superseded runs self-cancel). The 3 live zombies were also cancelled manually.

🤖 Generated with Claude Code

A workflow run that GitHub keeps listing as "queued" but never actually
dispatches to a runner — classically a run superseded by a newer commit
on a workflow without concurrency:cancel-in-progress — would make
ephemerd re-provision a full runner/VM for it on every seenTTL, forever.
Observed live: 3 stale php-sdk macOS jobs (19h old, 8 commits behind
HEAD, runner never assigned) each booted a fresh macOS VM every ~10 min
indefinitely, burning ~4GB + boot for nothing and inflating the queue.

Add a per-job provisioning-attempt counter. The seen dedup lets a given
job past provisioning only ~once per seenTTL, so a live job (which runs
to completion and then stops appearing) reaches it once, while a zombie
reappears each cycle. After maxProvisionAttempts (~50 min of retries)
the job is skipped with a one-time warning instead of re-provisioning.
The counter is pruned in cleanSeen once the job stops appearing, so a
later legitimate rerun of the same job id starts fresh.

Tests: TestHandleQueued_ZombieSkip (skip fires past the cap, never
claims) and TestCleanSeen_PrunesAttempts (counter reset on expiry).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants