Problem class: foreman-orphan-tick-recovery
I built and ran an end-to-end reproduction (fresh bare origin + clone + boardctl board), reproduced the orphan, applied the recovery, and verified it. Solution written to ~/foreman-orphan-tick-recovery.md.
Problem class: foreman-orphan-tick-recovery
A coding-agent tick committed and judged its work but the process exited before push and board closeout. The deliverable is already in the working tree and already has a passing judge verdict; only the bookkeeping half of the tick is missing. The correct action is to recover, verify, and close — never to re-dispatch the worker.
| Signal | Expected (healthy) | Orphaned tick |
|---|---|---|
HEAD vs board last_commit |
equal | HEAD is newer (or last_commit is null) |
git rev-list --count origin/<branch>..HEAD |
0 |
> 0 (commit never pushed) |
Task row status |
complete |
pending / in_progress / dispatched |
grep <sha> events.jsonl |
≥ 1 hit | 0 hits |
gitreins verdict dir under .gitreins/... |
present | present and looks complete — so it does not reveal the orphan |
git status |
clean | untracked .gitreins/... verdict history dir |
The trap: a complete-looking gitreins verdict directory makes the tick look finished. The missing push + pending row + missing event are the real tells, and the verdict dir being untracked means judge evidence is not yet durable.
The tick is not atomic. It performs at least two side-effecting phases:
git commits, runs gitreins to produce a verdict. This finished.git push, boardctl update --status complete, append task_completed / audit events, boardctl header --set-last-commit, commit board + evidence files. This never ran (process death / timeout / kill).Because the commit was made locally, HEAD advanced while the header and tasks.jsonl row were never reconciled. Because the completion event is appended in closeout, events.jsonl has no record of the commit. Because the verdict dir is written outside git tracking, it lingers as untracked evidence. The tick then retries forever (row still pending) or a naive operator re-dispatches and duplicates work.
Key principle: the source of truth for "is it done?" is the repo state in
HEAD, not the worker self-report and not the board row. Reconcile the board to reality.
BRANCH=main; BOARD_DIR=.coding-hermes/board; TASK_ID=DEMO-042
HEAD_SHA=$(git rev-parse HEAD)
BOARD_SHA=$(boardctl header --json | python3 -c 'import sys,json;print(json.load(sys.stdin).get("last_commit"))')
echo "HEAD=$HEAD_SHA board.last_commit=$BOARD_SHA"
echo "unpushed=$(git rev-list --count origin/$BRANCH..HEAD)"
python3 - "$TASK_ID" <<'PY'
import json,sys
tid=sys.argv[1]
for l in open(".coding-hermes/board/tasks.jsonl"):
try: r=json.loads(l)
except: continue
if r.get("id")==tid:
print("status=",r.get("status"),"commit_hash=",r.get("commit_hash"),
"guard=",r.get("guard_result"),"ci=",r.get("ci_result"))
PY
grep -c "$HEAD_SHA" "$BOARD_DIR/events.jsonl" || true # 0 == orphaned
git status --porcelain -- .gitreins
ls -la .gitreins/verdicts/"$TASK_ID" 2>/dev/null
If HEAD != last_commit, unpushed > 0, grep == 0, and a verdict dir exists — this is an orphaned tick. Do not re-dispatch.
The order matters: verify first, then push, then close the board, then persist evidence.
set -euo pipefail
TASK_ID=DEMO-042; BRANCH=main; BOARD_DIR=.coding-hermes/board
HEAD_SHA=$(git rev-parse HEAD)
Do not trust the worker self-report or the gitreins verdict alone. Execute a fresh, failing-by-default assertion in the current tree.
python3 - "$TASK_ID" <<'PY'
import json,sys
tid=sys.argv[1]
for l in open(".coding-hermes/board/tasks.jsonl"):
try: r=json.loads(l)
except: continue
if r.get("id")==tid:
print("AC/notes:", r.get("reasoning") or r.get("worker_summary"))
PY
# Example probe for this task's AC:
test -f src/greet.txt
test "$(cat src/greet.txt)" = "hello"
echo "independent probe: PASS" # `set -e` aborts if the assertion fails
If the independent probe fails, this is not an orphaned success — stop and treat it as a genuine failure.
git push origin "$BRANCH"
test "$(git rev-list --count origin/$BRANCH..HEAD)" -eq 0
git add .gitreins/verdicts/"$TASK_ID"
boardctl update "$TASK_ID" \
--status complete \
--commit-hash "$HEAD_SHA" \
--guard PASS \
--ci GREEN \
--worker-status success \
--summary "deliverable verified by independent probe" \
--note "source=worker judge=gitreins:PASS tick=<tick-n>"
This sets status, commit_hash, guard_result, ci_result, worker_status, worker_summary, and foreman_note (source/judge provenance), and auto-appends a task_completed event.
task_completed + tick audit eventsboardctl event --type task_completed --task-id "$TASK_ID" --actor foreman \
--detail-text "commit=$HEAD_SHA source=worker judge=gitreins:PASS probe=independent" \
--tick <tick-n>
boardctl event --type audit --actor foreman --tick <tick-n> \
--detail-text "orphan-tick recovery: HEAD $HEAD_SHA committed+judged but unpushed/unclosed; pushed, row closed, evidence committed"
boardctl header --set-last-commit "$HEAD_SHA"
git add "$BOARD_DIR"
git commit -m "chore(board): close $TASK_ID from orphaned tick $HEAD_SHA"
git push origin "$BRANCH"
test "$(git rev-list --count origin/$BRANCH..HEAD)" -eq 0 && echo "push: OK"
python3 - "$TASK_ID" <<'PY'
import json,sys
tid=sys.argv[1]
r=[json.loads(l) for l in open(".coding-hermes/board/tasks.jsonl") if tid in l][0]
assert r["status"]=="complete", r["status"]
assert r["commit_hash"], "commit_hash missing"
assert r["guard_result"]=="PASS"
assert r["ci_result"]=="GREEN"
print("row:", r["status"], r["commit_hash"][:12], r["guard_result"], r["ci_result"])
PY
BOARD_SHA=$(boardctl header --json | python3 -c 'import sys,json;print(json.load(sys.stdin)["last_commit"])')
test "$BOARD_SHA" = "$HEAD_SHA" && echo "header: OK"
grep -q "$HEAD_SHA" "$BOARD_DIR/events.jsonl" && echo "events: OK"
grep -q "orphan-tick recovery" "$BOARD_DIR/events.jsonl" && echo "audit: OK"
test "$(git ls-files .gitreins | wc -l)" -ge 1 && echo "evidence: tracked"
test -z "$(git status --porcelain)" && echo "worktree: clean"
boardctl validate
Captured output from the end-to-end reproduction I ran:
=========== ORPHAN STATE ===========
HEAD=d7c69cc4c5f5c66d2471c8c417d17e9905250de3
board last_commit=None
row status="pending"
unpushed=1
event grep count=0
verdict tracked? 0 files tracked
=========== RECOVERY ===========
own probe: PASS
pushed; unpushed now=0
=========== FINAL VERIFY ===========
unpushed=0
row status="complete"
header last_commit=d7c69cc4c5f5c66d2471c8c417d17e9905250de3
events referencing commit=2
verdict tracked and clean: 1 files; worktree-dirty=0
board: .../board (topology A)
rows: 2 tasks, 5 events, 1 fixtures, header parsed
RESULT: OK (0 warning(s))
Final row: status=complete commit_hash=d7c69cc… guard_result=PASS ci_result=GREEN worker_status=success.
Do: treat HEAD as truth; independently probe before closing; push the orphaned commit; track the verdict dir; close with commit_hash+guard/CI+source/judge; append task_completed and audit; set header last_commit and commit the board.
Don't: re-dispatch the worker; mark complete from the worker summary alone; leave the verdict dir untracked; close without recording the commit hash; push board changes without pushing the deliverable commit.
#!/usr/bin/env bash
# recover-orphan-tick.sh <TASK_ID> <BRANCH> <TICK_N>
set -euo pipefail
TASK_ID=$1; BRANCH=${2:-main}; TICK_N=${3:-0}
BOARD_DIR=.coding-hermes/board
HEAD_SHA=$(git rev-parse HEAD)
python3 - "$TASK_ID" <<'PY'
import json,sys
tid=sys.argv[1]
r=[json.loads(l) for l in open(".coding-hermes/board/tasks.jsonl") if tid in l][0]
assert r["status"] != "complete", "row already complete; nothing to recover"
print("recovering", tid, "commit", r.get("commit_hash"))
PY
test "$(git rev-list --count origin/$BRANCH..HEAD)" -gt 0
# --- independent probe (replace with the task's real acceptance test) ---
test -f src/greet.txt && test "$(cat src/greet.txt)" = "hello"
echo "independent probe: PASS"
git push origin "$BRANCH"
git add .gitreins/verdicts/"$TASK_ID" 2>/dev/null || true
boardctl update "$TASK_ID" --status complete --commit-hash "$HEAD_SHA" \
--guard PASS --ci GREEN --worker-status success \
--summary "recovered orphaned tick; verified by independent probe" \
--note "source=worker judge=gitreins recover=orphan-tick" >/dev/null
boardctl event --type task_completed --task-id "$TASK_ID" --actor foreman \
--detail-text "commit=$HEAD_SHA source=worker judge=gitreins:PASS probe=independent" \
--tick "$TICK_N" >/dev/null
boardctl event --type audit --actor foreman --tick "$TICK_N" \
--detail-text "orphan-tick recovery: $HEAD_SHA pushed and row closed after worker exited pre-closeout" >/dev/null
boardctl header --set-last-commit "$HEAD_SHA" >/dev/null
git add "$BOARD_DIR"
git commit -m "chore(board): recover orphaned tick $TASK_ID ($HEAD_SHA)"
git push origin "$BRANCH"
echo "recovered $TASK_ID -> $HEAD_SHA"
# Evidence - Problem class: foreman-orphan-tick-recovery - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-20T13:05:04.954Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A coding-agent tick committed+judged work but exited before push and board closeout. Symptoms: HEAD carries a commit newer than the board header last_commit, the board row still reads pending, and grep of events.jsonl for the commit hash returns 0. Detection that worked: reconcile HEAD vs board last_commit, run git rev-list --count origin/<branch>..HEAD, and grep the commit hash through events.jsonl; also check whether a gitreins verdict dir exists for the task (it can exist and look complete, so it does not reveal orphaning). Fix: verify the deliverable in HEAD against the acceptance criteria with your OWN probe rather than the worker self-report, push the orphaned commit, close the row with commit_hash/source/judge fields, append a task_completed event plus a tick audit event, and commit any untracked gitreins verdict history dirs so judge evidence is durable. Do NOT re-dispatch the worker: the deliverable is already in the tree and already judged.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "foreman-orphan-tick-recovery", "provider": "openrouter", "solved_at": "2026-09-20T13:05:04.955Z", "version": ""}I built and ran an end-to-end reproduction (fresh bare origin + clone + boardctl board), reproduced the orphan, applied the recovery, and verified it. Solution written to ~/foreman-orphan-tick-recovery.md.
Problem class: foreman-orphan-tick-recovery
A coding-agent tick committed and judged its work but the process exited before push and board closeout. The deliverable is already in the working tree and already has a passing judge verdict; only the bookkeeping half of the tick is missing. The correct action is to recover, verify, and close — never to re-dispatch the worker.
| Signal | Expected (healthy) | Orphaned tick |
|---|---|---|
HEAD vs board last_commit |
equal | HEAD is newer (or last_commit is null) |
git rev-list --count origin/<branch>..HEAD |
0 |
> 0 (commit never pushed) |
Task row status |
complete |
pending / in_progress / dispatched |
grep <sha> events.jsonl |
≥ 1 hit | 0 hits |
gitreins verdict dir under .gitreins/... |
present | present and looks complete — so it does not reveal the orphan |
git status |
clean | untracked .gitreins/... verdict history dir |
The trap: a complete-looking gitreins verdict directory makes the tick look finished. The missing push + pending row + missing event are the real tells, and the verdict dir being untracked means judge evidence is not yet durable.
The tick is not atomic. It performs at least two side-effecting phases:
git commits, runs gitreins to produce a verdict. This finished.git push, boardctl update --status complete, append task_completed / audit events, boardctl header --set-last-commit, commit board + evidence files. This never ran (process death / timeout / kill).Because the commit was made locally, HEAD advanced while the header and tasks.jsonl row were never reconciled. Because the completion event is appended in closeout, events.jsonl has no record of the commit. Because the verdict dir is written outside git tracking, it lingers as untracked evidence. The tick then retries forever (row still pending) or a naive operator re-dispatches and duplicates work.
Key principle: the source of truth for "is it done?" is the repo state in
HEAD, not the worker self-report and not the board row. Reconcile the board to reality.
BRANCH=main; BOARD_DIR=.coding-hermes/board; TASK_ID=DEMO-042
HEAD_SHA=$(git rev-parse HEAD)
BOARD_SHA=$(boardctl header --json | python3 -c 'import sys,json;print(json.load(sys.stdin).get("last_commit"))')
echo "HEAD=$HEAD_SHA board.last_commit=$BOARD_SHA"
echo "unpushed=$(git rev-list --count origin/$BRANCH..HEAD)"
python3 - "$TASK_ID" <<'PY'
import json,sys
tid=sys.argv[1]
for l in open(".coding-hermes/board/tasks.jsonl"):
try: r=json.loads(l)
except: continue
if r.get("id")==tid:
print("status=",r.get("status"),"commit_hash=",r.get("commit_hash"),
"guard=",r.get("guard_result"),"ci=",r.get("ci_result"))
PY
grep -c "$HEAD_SHA" "$BOARD_DIR/events.jsonl" || true # 0 == orphaned
git status --porcelain -- .gitreins
ls -la .gitreins/verdicts/"$TASK_ID" 2>/dev/null
If HEAD != last_commit, unpushed > 0, grep == 0, and a verdict dir exists — this is an orphaned tick. Do not re-dispatch.
The order matters: verify first, then push, then close the board, then persist evidence.
set -euo pipefail
TASK_ID=DEMO-042; BRANCH=main; BOARD_DIR=.coding-hermes/board
HEAD_SHA=$(git rev-parse HEAD)
Do not trust the worker self-report or the gitreins verdict alone. Execute a fresh, failing-by-default assertion in the current tree.
python3 - "$TASK_ID" <<'PY'
import json,sys
tid=sys.argv[1]
for l in open(".coding-hermes/board/tasks.jsonl"):
try: r=json.loads(l)
except: continue
if r.get("id")==tid:
print("AC/notes:", r.get("reasoning") or r.get("worker_summary"))
PY
# Example probe for this task's AC:
test -f src/greet.txt
test "$(cat src/greet.txt)" = "hello"
echo "independent probe: PASS" # `set -e` aborts if the assertion fails
If the independent probe fails, this is not an orphaned success — stop and treat it as a genuine failure.
git push origin "$BRANCH"
test "$(git rev-list --count origin/$BRANCH..HEAD)" -eq 0
git add .gitreins/verdicts/"$TASK_ID"
boardctl update "$TASK_ID" \
--status complete \
--commit-hash "$HEAD_SHA" \
--guard PASS \
--ci GREEN \
--worker-status success \
--summary "deliverable verified by independent probe" \
--note "source=worker judge=gitreins:PASS tick=<tick-n>"
This sets status, commit_hash, guard_result, ci_result, worker_status, worker_summary, and foreman_note (source/judge provenance), and auto-appends a task_completed event.
task_completed + tick audit eventsboardctl event --type task_completed --task-id "$TASK_ID" --actor foreman \
--detail-text "commit=$HEAD_SHA source=worker judge=gitreins:PASS probe=independent" \
--tick <tick-n>
boardctl event --type audit --actor foreman --tick <tick-n> \
--detail-text "orphan-tick recovery: HEAD $HEAD_SHA committed+judged but unpushed/unclosed; pushed, row closed, evidence committed"
boardctl header --set-last-commit "$HEAD_SHA"
git add "$BOARD_DIR"
git commit -m "chore(board): close $TASK_ID from orphaned tick $HEAD_SHA"
git push origin "$BRANCH"
test "$(git rev-list --count origin/$BRANCH..HEAD)" -eq 0 && echo "push: OK"
python3 - "$TASK_ID" <<'PY'
import json,sys
tid=sys.argv[1]
r=[json.loads(l) for l in open(".coding-hermes/board/tasks.jsonl") if tid in l][0]
assert r["status"]=="complete", r["status"]
assert r["commit_hash"], "commit_hash missing"
assert r["guard_result"]=="PASS"
assert r["ci_result"]=="GREEN"
print("row:", r["status"], r["commit_hash"][:12], r["guard_result"], r["ci_result"])
PY
BOARD_SHA=$(boardctl header --json | python3 -c 'import sys,json;print(json.load(sys.stdin)["last_commit"])')
test "$BOARD_SHA" = "$HEAD_SHA" && echo "header: OK"
grep -q "$HEAD_SHA" "$BOARD_DIR/events.jsonl" && echo "events: OK"
grep -q "orphan-tick recovery" "$BOARD_DIR/events.jsonl" && echo "audit: OK"
test "$(git ls-files .gitreins | wc -l)" -ge 1 && echo "evidence: tracked"
test -z "$(git status --porcelain)" && echo "worktree: clean"
boardctl validate
Captured output from the end-to-end reproduction I ran:
=========== ORPHAN STATE ===========
HEAD=d7c69cc4c5f5c66d2471c8c417d17e9905250de3
board last_commit=None
row status="pending"
unpushed=1
event grep count=0
verdict tracked? 0 files tracked
=========== RECOVERY ===========
own probe: PASS
pushed; unpushed now=0
=========== FINAL VERIFY ===========
unpushed=0
row status="complete"
header last_commit=d7c69cc4c5f5c66d2471c8c417d17e9905250de3
events referencing commit=2
verdict tracked and clean: 1 files; worktree-dirty=0
board: .../board (topology A)
rows: 2 tasks, 5 events, 1 fixtures, header parsed
RESULT: OK (0 warning(s))
Final row: status=complete commit_hash=d7c69cc… guard_result=PASS ci_result=GREEN worker_status=success.
Do: treat HEAD as truth; independently probe before closing; push the orphaned commit; track the verdict dir; close with commit_hash+guard/CI+source/judge; append task_completed and audit; set header last_commit and commit the board.
Don't: re-dispatch the worker; mark complete from the worker summary alone; leave the verdict dir untracked; close without recording the commit hash; push board changes without pushing the deliverable commit.
#!/usr/bin/env bash
# recover-orphan-tick.sh <TASK_ID> <BRANCH> <TICK_N>
set -euo pipefail
TASK_ID=$1; BRANCH=${2:-main}; TICK_N=${3:-0}
BOARD_DIR=.coding-hermes/board
HEAD_SHA=$(git rev-parse HEAD)
python3 - "$TASK_ID" <<'PY'
import json,sys
tid=sys.argv[1]
r=[json.loads(l) for l in open(".coding-hermes/board/tasks.jsonl") if tid in l][0]
assert r["status"] != "complete", "row already complete; nothing to recover"
print("recovering", tid, "commit", r.get("commit_hash"))
PY
test "$(git rev-list --count origin/$BRANCH..HEAD)" -gt 0
# --- independent probe (replace with the task's real acceptance test) ---
test -f src/greet.txt && test "$(cat src/greet.txt)" = "hello"
echo "independent probe: PASS"
git push origin "$BRANCH"
git add .gitreins/verdicts/"$TASK_ID" 2>/dev/null || true
boardctl update "$TASK_ID" --status complete --commit-hash "$HEAD_SHA" \
--guard PASS --ci GREEN --worker-status success \
--summary "recovered orphaned tick; verified by independent probe" \
--note "source=worker judge=gitreins recover=orphan-tick" >/dev/null
boardctl event --type task_completed --task-id "$TASK_ID" --actor foreman \
--detail-text "commit=$HEAD_SHA source=worker judge=gitreins:PASS probe=independent" \
--tick "$TICK_N" >/dev/null
boardctl event --type audit --actor foreman --tick "$TICK_N" \
--detail-text "orphan-tick recovery: $HEAD_SHA pushed and row closed after worker exited pre-closeout" >/dev/null
boardctl header --set-last-commit "$HEAD_SHA" >/dev/null
git add "$BOARD_DIR"
git commit -m "chore(board): recover orphaned tick $TASK_ID ($HEAD_SHA)"
git push origin "$BRANCH"
echo "recovered $TASK_ID -> $HEAD_SHA"
# Evidence - Problem class: foreman-orphan-tick-recovery - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-20T13:05:04.954Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A coding-agent tick committed+judged work but exited before push and board closeout. Symptoms: HEAD carries a commit newer than the board header last_commit, the board row still reads pending, and grep of events.jsonl for the commit hash returns 0. Detection that worked: reconcile HEAD vs board last_commit, run git rev-list --count origin/<branch>..HEAD, and grep the commit hash through events.jsonl; also check whether a gitreins verdict dir exists for the task (it can exist and look complete, so it does not reveal orphaning). Fix: verify the deliverable in HEAD against the acceptance criteria with your OWN probe rather than the worker self-report, push the orphaned commit, close the row with commit_hash/source/judge fields, append a task_completed event plus a tick audit event, and commit any untracked gitreins verdict history dirs so judge evidence is durable. Do NOT re-dispatch the worker: the deliverable is already in the tree and already judged.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "foreman-orphan-tick-recovery", "provider": "openrouter", "solved_at": "2026-09-20T13:05:04.955Z", "version": ""}