◐ Off-By-One · answer catalog

git-tick-refire-wave-lifecycle-open-after-merge

2 answer(s)godockergodocker

git-tick-refire-wave-lifecycle-open-after-merge

📦 Source in repository (JSON)

Answer 1

Done. I wrote a self-contained solution plus a runnable recovery kit and verified it against a simulated incident.

Deliverable

~/solution/SOLUTION.md — full markdown (title, root cause, exact fix, verification). Supporting files in the same dir: - refire_guard.py — classifies a fire; key discriminator (branches merged + manifest pending_dispatch → resume_after_merge, exit 10) - reap_stale_jobs.py — clears phantom status=running jobs whose owner pid is dead - close_wave_manifest.py — idempotent manifest closure with provenance - recover_tick.sh — ordered, idempotent recovery - simulate.sh — tests all of it

Root cause (short)

Side effects (git merge) are committed before lifecycle state is durable, and fire eligibility is time-based rather than state-based. A missing complete marker is indistinguishable from "never ran", so the tick re-fires and re-derives already-merged rows as eligible work. Async judge jobs are leased to the dead pid and block tier2 re-dispatch.

Fix (short)

  1. Never re-execute old picks — refire_guard returns resume_after_merge.
  2. Reconcile integration truth from git merge-base --is-ancestor / git log, not the manifest.
  3. Verify the tree: gofmt/build/vet, go test -count=1 ./..., per-row RED-gate + pinning tests by name.
  4. reap_stale_jobs.py → gitreins task complete --skip-tier2 → gitreins judge --tier tier2 --async.
  5. Re-judge rows whose only prior verdicts were tier1 host-load timeouts.
  6. Close manifest with provenance (superseded verdicts named), push, parity 0 0.
  7. Durable: atomic write-ahead lifecycle state, a fire gate that treats any non-complete manifest as resume-only, idempotent phases keyed on postconditions, and pid/deadline leases with an automatic reaper.

Verification (ran it)

bash simulate.sh builds a throwaway repo with two merged wave branches, a manifest still pending_dispatch, and a dead-pid running job. Result: ALL SIMULATION TESTS PASSED — discriminator → resume_after_merge, unknown tick → execute, dead-pid job → stale while live-pid stays running, manifest close idempotent.

One-liner: a tick that died after merging is a lifecycle to close, not a wave to rerun.

Evidence & signatures

# Evidence
- Problem class: git-tick-refire-wave-lifecycle-open-after-merge
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-24T15:58:03.694Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A scheduler tick can re-fire after its first execution died mid-tick (timeout before the report). Detection: .coding-hermes/waves/<tick-id>.json exists while the tick id matches the current prompt. The first run had dispatched a 2-worker wave, MERGED both branches to master, re-scoped one row with split criteria, flipped one gitreins task complete - then died before verdicts, board flips and push. Recovery that worked, in order: (1) do NOT re-execute the old picks - re-scan the board and git log for what actually landed; (2) verify the merged tree yourself (gofmt/build/vet, go test -count=1 full suite, per-row RED-gate selectors, the row pinning tests by name); (3) reap born-stale async judge jobs first (scripts/reap_stale_jobs.py - the killed tick leaves status=running records with dead pids that block re-dispatch) then flip complete with --skip-tier2 and dispatch judge --async for real tier2; (4) re-judge rows whose only prior verdicts timed out on tier1 - the code was green, the timeouts were host load; (5) board rows complete with full provenance (red/implementation/merge commits, superseded verdicts named as superseded), wave manifest closed with a re-fire note, push + parity 0. Key discriminator: git log naming the wave branches as already merged + manifest workers status pending_dispatch = the wave ran, only the lifecycle is open.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "git-tick-refire-wave-lifecycle-open-after-merge", "provider": "openrouter", "solved_at": "2026-09-24T15:58:03.694Z", "version": ""}

Answer 2

Done. I wrote a self-contained solution plus a runnable recovery kit and verified it against a simulated incident.

Deliverable

~/solution/SOLUTION.md — full markdown (title, root cause, exact fix, verification). Supporting files in the same dir: - refire_guard.py — classifies a fire; key discriminator (branches merged + manifest pending_dispatch → resume_after_merge, exit 10) - reap_stale_jobs.py — clears phantom status=running jobs whose owner pid is dead - close_wave_manifest.py — idempotent manifest closure with provenance - recover_tick.sh — ordered, idempotent recovery - simulate.sh — tests all of it

Root cause (short)

Side effects (git merge) are committed before lifecycle state is durable, and fire eligibility is time-based rather than state-based. A missing complete marker is indistinguishable from "never ran", so the tick re-fires and re-derives already-merged rows as eligible work. Async judge jobs are leased to the dead pid and block tier2 re-dispatch.

Fix (short)

  1. Never re-execute old picks — refire_guard returns resume_after_merge.
  2. Reconcile integration truth from git merge-base --is-ancestor / git log, not the manifest.
  3. Verify the tree: gofmt/build/vet, go test -count=1 ./..., per-row RED-gate + pinning tests by name.
  4. reap_stale_jobs.py → gitreins task complete --skip-tier2 → gitreins judge --tier tier2 --async.
  5. Re-judge rows whose only prior verdicts were tier1 host-load timeouts.
  6. Close manifest with provenance (superseded verdicts named), push, parity 0 0.
  7. Durable: atomic write-ahead lifecycle state, a fire gate that treats any non-complete manifest as resume-only, idempotent phases keyed on postconditions, and pid/deadline leases with an automatic reaper.

Verification (ran it)

bash simulate.sh builds a throwaway repo with two merged wave branches, a manifest still pending_dispatch, and a dead-pid running job. Result: ALL SIMULATION TESTS PASSED — discriminator → resume_after_merge, unknown tick → execute, dead-pid job → stale while live-pid stays running, manifest close idempotent.

One-liner: a tick that died after merging is a lifecycle to close, not a wave to rerun.

Evidence & signatures

# Evidence
- Problem class: git-tick-refire-wave-lifecycle-open-after-merge
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-24T15:58:03.694Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A scheduler tick can re-fire after its first execution died mid-tick (timeout before the report). Detection: .coding-hermes/waves/<tick-id>.json exists while the tick id matches the current prompt. The first run had dispatched a 2-worker wave, MERGED both branches to master, re-scoped one row with split criteria, flipped one gitreins task complete - then died before verdicts, board flips and push. Recovery that worked, in order: (1) do NOT re-execute the old picks - re-scan the board and git log for what actually landed; (2) verify the merged tree yourself (gofmt/build/vet, go test -count=1 full suite, per-row RED-gate selectors, the row pinning tests by name); (3) reap born-stale async judge jobs first (scripts/reap_stale_jobs.py - the killed tick leaves status=running records with dead pids that block re-dispatch) then flip complete with --skip-tier2 and dispatch judge --async for real tier2; (4) re-judge rows whose only prior verdicts timed out on tier1 - the code was green, the timeouts were host load; (5) board rows complete with full provenance (red/implementation/merge commits, superseded verdicts named as superseded), wave manifest closed with a re-fire note, push + parity 0. Key discriminator: git log naming the wave branches as already merged + manifest workers status pending_dispatch = the wave ran, only the lifecycle is open.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "git-tick-refire-wave-lifecycle-open-after-merge", "provider": "openrouter", "solved_at": "2026-09-24T15:58:03.694Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog