Scope: coding-hermes foreman tick · git worktree waves · gitreins lifecycle · Python
I reproduced the incident end-to-end in a synthetic repo and verified every recovery step. The full self-contained solution is written to /workspace/solution.md.
board-foreman-wave-recovery)Scope: coding-hermes foreman tick · git worktree waves · gitreins lifecycle · Python
Incident: <project> tick 277, wave <project>-2026-09-24-22-15-26, tasks DF-CHIMERA-V2-49, DF-CHIMERA-V2-50
Key risk: branch suites must run against the worktree via PYTHONPATH=<worktree>/src, never the host venv.
A prior tick composed the wave, created a git worktree per task, and let each worker commit on its own wt/* branch, then died before the gitreins lifecycle, board writes, and push. Because dispatch is side-effecting and happened before any durable bookkeeping, the system is inconsistent:
| Signal | Meaning |
|---|---|
.coding-hermes/waves/<tick-id>.json untracked, finished_at == null |
Wave composed, never finalized |
Worktree tips ahead of manifest base_commit |
Workers produced real commits |
git branch --list 'wt/*' shows branches |
Work survives even if worktrees are pruned |
events.jsonl has no task_dispatched rows |
Owning tick never recorded dispatch |
Naive re-dispatch is destructive — it would branch again from base and duplicate/collide with existing branches. The correct action is to adopt and complete the orphaned wave. Three traps make a careless recovery look successful:
main/src, so pytest run in a worktree imports main's code and falsely passes.REPO=~/<project>
MANIFEST=$(ls -t "$REPO"/.coding-hermes/waves/*.json | head -1)
python3 - "$MANIFEST" <<'PY'
import json,sys
m=json.load(open(sys.argv[1])); assert m.get("finished_at") is None
print("wave",m.get("tick"),"base",m["base_commit"])
for t in m["tasks"]: print(t["id"], t["branch"], t["worktree"])
PY
BASE=$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1]))["base_commit"])' "$MANIFEST")
git -C "$REPO" branch --list 'wt/*'
while read -r b; do echo "ahead $b: $(git -C "$REPO" rev-list --count "$BASE".."$b")"; done \
< <(git -C "$REPO" branch --list 'wt/*' --format='%(refname:short)')
grep -c '"task_dispatched"' "$REPO"/.coding-hermes/events.jsonl 2>/dev/null || echo 0
Take an adoption lock (e.g. O_EXCL create of <tick-id>.recovering) before mutating.
sys.pathVENV="$REPO/.venv/bin/python"
for wt in "$REPO"-DF-CHIMERA-V2-49 "$REPO"-DF-CHIMERA-V2-50; do
PYTHONPATH="$wt/src" "$VENV" -c \
"import chimera; print(chimera.__file__); assert chimera.__file__.startswith('$wt/'), 'SHADOWED!'"
PYTHONPATH="$wt/src" "$VENV" -m pytest -q "$wt/tests"
done
PYTHONPATH entries are prepended to sys.path, ahead of site-packages, so they beat the editable-install finder.
--no-ff + merge gatecd "$REPO"; git checkout main; git status --porcelain # must be clean
BASE=$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1]))["base_commit"])' "$MANIFEST")
for b in wt/DF-CHIMERA-V2-49 wt/DF-CHIMERA-V2-50; do
git merge --no-ff -m "chore(wave): integrate $b into main" "$b"
done
"$VENV" -m pytest -q # MERGE GATE on merged main
Reconcile cross-task criteria in tests (do not revert a whole branch). Here tests_49.py had to adopt worker 50's new-scope criterion; re-run until green.
TIP=$(git rev-parse HEAD)
find . -path ./.git -prune -o -name '__pycache__' -type d -print0 | xargs -0 rm -rf
find . -path ./.git -prune -o -name '*.pyc' -print -delete
git reset --soft "$BASE" && git add -A
git diff --cached --quiet "$BASE" && { echo "FATAL: empty index -> DEGRADED" >&2; exit 1; }
git diff --cached --stat "$BASE"
for id in DF-CHIMERA-V2-49 DF-CHIMERA-V2-50; do
gitreins tier2 --task "$id" --diff "$(git diff --cached --binary "$BASE")"
done
git reset "$TIP" # mixed reset keeps worktree -> tasks.yaml survives
git status --short # .gitreins/tasks.yaml expected modified
git add .gitreins/tasks.yaml
git commit -m "chore(gitreins): finalize wave <project>-2026-09-24-22-15-26 tasks"
WAVE_COMMIT=$(git rev-parse HEAD)
python3 - "$MANIFEST" <<'PY'
import json,sys,datetime
p=sys.argv[1]; m=json.load(open(p))
m["finished_at"]=datetime.datetime.utcnow().isoformat()+"Z"
json.dump(m,open(p,"w"),indent=2)
PY
python3 recover_wave.py close --project "$REPO" --tick 277 \
--tasks DF-CHIMERA-V2-49 DF-CHIMERA-V2-50 --commit "$WAVE_COMMIT"
git push origin main && git push backup main
recover_wave.py appends one final row per id and asserts by reading the board back:
def close(args):
for tid in args.tasks:
append(args.project, {"id": tid, "tick": args.tick, "state": "done",
"commit": args.commit, "recovered": True})
rows = board_rows(args.project)
for tid in args.tasks:
last = [r for r in rows if r.get("id") == tid][-1]
assert last["state"] == "done" and last["commit"] == args.commit, \
f"board read-back failed for {tid}: {last}"
[ "$(git branch --list 'wt/*' | wc -l)" -eq 2 ] # no re-dispatch
PYTHONPATH="$REPO-DF-CHIMERA-V2-50/src" "$VENV" -c \
"import chimera; assert chimera.__file__.startswith('$REPO-DF-CHIMERA-V2-50/')" # no shadow
"$VENV" -m pytest -q # merge gate green
git reset --soft "$BASE" && git add -A && ! git diff --cached --quiet "$BASE" # non-empty index
git reset "$TIP"; [ "$(git rev-parse HEAD)" = "$TIP" ] # chain restored
grep -q 'state: passed' .gitreins/tasks.yaml # judged yaml kept
git branch -r --contains "$WAVE_COMMIT" # pushed to both remotes
main-repo)/tmp/sim/main-repo/src/chimera/__init__.py and failed assert scope()=="new-scope"; with PYTHONPATH=<worktree>/src it passed against the worktree copy.git merge --no-ff reported "Merge made by the 'ort' strategy" for both branches (clean auto-merge), yet the merged suite failed tests_49.py::test_49_scope_semantics - AssertionError: assert 'new-sco..., 1 failed, 1 passed. After reconciliation: 2 passed.git add -A gave 9 files changed, 10 insertions(+), 1 deletion(-) and NONEMPTY-OK.git reset "$TIP" restored HEAD while .gitreins/tasks.yaml stayed modified with state: passed.wt/* branches.PYTHONPATH=<worktree>/src and assert the import path.__pycache__/*.pyc before staging the judge diff.tasks.yaml survives.git branch -r --contains.The full document lives at /workspace/solution.md.
# Evidence - Problem class: board-foreman-wave-recovery - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-25T16:41:17.110Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Foreman tick recovers an aborted prior tick's wave: the prior session composed a wave manifest and dispatched workers into their own git worktrees, then died BEFORE the gitreins lifecycle, board writes, or push. Discovery signals that the wave ran: (1) an untracked .coding-hermes/waves/<tick-id>.json manifest with no finished_at; (2) worktrees at <repo>-<taskid> paths whose branch tips sit AHEAD of the manifest's recorded base commit; (3) git branch --list 'wt/*' shows the wave branches; (4) events.jsonl has NO task_dispatched rows for the wave tasks. Recovery path proven this tick: verify each branch's suite via PYTHONPATH=<worktree>/src pytest (the main venv's editable install shadows branch code otherwise and silently tests main), merge serially with --no-ff onto main, run a merge gate on merged main for shared-file interactions (two workers editing one file can interact semantically even with a clean textual auto-merge - here a new test asserted the pre-change scope semantics another worker's criterion had replaced), soft-reset to the pre-work base + stage the full work diff so judge-time tier-1 grades the real diff (empty index = skip-poisoned DEGRADED), run tier2 per task, restore the chain with a mixed reset (git reset <tip>) while preserving the judged .gitreins/tasks.yaml, then record commit, close rows last-row-per-id with a read-back assert, and push both remotes.", "environment": "coding-hermes foreman tick, git worktree waves, gitreins lifecycle", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "board-foreman-wave-recovery", "provider": "openrouter", "solved_at": "2026-09-25T16:41:17.110Z", "version": ""}I reproduced the incident end-to-end in a synthetic repo and verified every recovery step. The full self-contained solution is written to /workspace/solution.md.
board-foreman-wave-recovery)Scope: coding-hermes foreman tick · git worktree waves · gitreins lifecycle · Python
Incident: <project> tick 277, wave <project>-2026-09-24-22-15-26, tasks DF-CHIMERA-V2-49, DF-CHIMERA-V2-50
Key risk: branch suites must run against the worktree via PYTHONPATH=<worktree>/src, never the host venv.
A prior tick composed the wave, created a git worktree per task, and let each worker commit on its own wt/* branch, then died before the gitreins lifecycle, board writes, and push. Because dispatch is side-effecting and happened before any durable bookkeeping, the system is inconsistent:
| Signal | Meaning |
|---|---|
.coding-hermes/waves/<tick-id>.json untracked, finished_at == null |
Wave composed, never finalized |
Worktree tips ahead of manifest base_commit |
Workers produced real commits |
git branch --list 'wt/*' shows branches |
Work survives even if worktrees are pruned |
events.jsonl has no task_dispatched rows |
Owning tick never recorded dispatch |
Naive re-dispatch is destructive — it would branch again from base and duplicate/collide with existing branches. The correct action is to adopt and complete the orphaned wave. Three traps make a careless recovery look successful:
main/src, so pytest run in a worktree imports main's code and falsely passes.REPO=~/<project>
MANIFEST=$(ls -t "$REPO"/.coding-hermes/waves/*.json | head -1)
python3 - "$MANIFEST" <<'PY'
import json,sys
m=json.load(open(sys.argv[1])); assert m.get("finished_at") is None
print("wave",m.get("tick"),"base",m["base_commit"])
for t in m["tasks"]: print(t["id"], t["branch"], t["worktree"])
PY
BASE=$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1]))["base_commit"])' "$MANIFEST")
git -C "$REPO" branch --list 'wt/*'
while read -r b; do echo "ahead $b: $(git -C "$REPO" rev-list --count "$BASE".."$b")"; done \
< <(git -C "$REPO" branch --list 'wt/*' --format='%(refname:short)')
grep -c '"task_dispatched"' "$REPO"/.coding-hermes/events.jsonl 2>/dev/null || echo 0
Take an adoption lock (e.g. O_EXCL create of <tick-id>.recovering) before mutating.
sys.pathVENV="$REPO/.venv/bin/python"
for wt in "$REPO"-DF-CHIMERA-V2-49 "$REPO"-DF-CHIMERA-V2-50; do
PYTHONPATH="$wt/src" "$VENV" -c \
"import chimera; print(chimera.__file__); assert chimera.__file__.startswith('$wt/'), 'SHADOWED!'"
PYTHONPATH="$wt/src" "$VENV" -m pytest -q "$wt/tests"
done
PYTHONPATH entries are prepended to sys.path, ahead of site-packages, so they beat the editable-install finder.
--no-ff + merge gatecd "$REPO"; git checkout main; git status --porcelain # must be clean
BASE=$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1]))["base_commit"])' "$MANIFEST")
for b in wt/DF-CHIMERA-V2-49 wt/DF-CHIMERA-V2-50; do
git merge --no-ff -m "chore(wave): integrate $b into main" "$b"
done
"$VENV" -m pytest -q # MERGE GATE on merged main
Reconcile cross-task criteria in tests (do not revert a whole branch). Here tests_49.py had to adopt worker 50's new-scope criterion; re-run until green.
TIP=$(git rev-parse HEAD)
find . -path ./.git -prune -o -name '__pycache__' -type d -print0 | xargs -0 rm -rf
find . -path ./.git -prune -o -name '*.pyc' -print -delete
git reset --soft "$BASE" && git add -A
git diff --cached --quiet "$BASE" && { echo "FATAL: empty index -> DEGRADED" >&2; exit 1; }
git diff --cached --stat "$BASE"
for id in DF-CHIMERA-V2-49 DF-CHIMERA-V2-50; do
gitreins tier2 --task "$id" --diff "$(git diff --cached --binary "$BASE")"
done
git reset "$TIP" # mixed reset keeps worktree -> tasks.yaml survives
git status --short # .gitreins/tasks.yaml expected modified
git add .gitreins/tasks.yaml
git commit -m "chore(gitreins): finalize wave <project>-2026-09-24-22-15-26 tasks"
WAVE_COMMIT=$(git rev-parse HEAD)
python3 - "$MANIFEST" <<'PY'
import json,sys,datetime
p=sys.argv[1]; m=json.load(open(p))
m["finished_at"]=datetime.datetime.utcnow().isoformat()+"Z"
json.dump(m,open(p,"w"),indent=2)
PY
python3 recover_wave.py close --project "$REPO" --tick 277 \
--tasks DF-CHIMERA-V2-49 DF-CHIMERA-V2-50 --commit "$WAVE_COMMIT"
git push origin main && git push backup main
recover_wave.py appends one final row per id and asserts by reading the board back:
def close(args):
for tid in args.tasks:
append(args.project, {"id": tid, "tick": args.tick, "state": "done",
"commit": args.commit, "recovered": True})
rows = board_rows(args.project)
for tid in args.tasks:
last = [r for r in rows if r.get("id") == tid][-1]
assert last["state"] == "done" and last["commit"] == args.commit, \
f"board read-back failed for {tid}: {last}"
[ "$(git branch --list 'wt/*' | wc -l)" -eq 2 ] # no re-dispatch
PYTHONPATH="$REPO-DF-CHIMERA-V2-50/src" "$VENV" -c \
"import chimera; assert chimera.__file__.startswith('$REPO-DF-CHIMERA-V2-50/')" # no shadow
"$VENV" -m pytest -q # merge gate green
git reset --soft "$BASE" && git add -A && ! git diff --cached --quiet "$BASE" # non-empty index
git reset "$TIP"; [ "$(git rev-parse HEAD)" = "$TIP" ] # chain restored
grep -q 'state: passed' .gitreins/tasks.yaml # judged yaml kept
git branch -r --contains "$WAVE_COMMIT" # pushed to both remotes
main-repo)/tmp/sim/main-repo/src/chimera/__init__.py and failed assert scope()=="new-scope"; with PYTHONPATH=<worktree>/src it passed against the worktree copy.git merge --no-ff reported "Merge made by the 'ort' strategy" for both branches (clean auto-merge), yet the merged suite failed tests_49.py::test_49_scope_semantics - AssertionError: assert 'new-sco..., 1 failed, 1 passed. After reconciliation: 2 passed.git add -A gave 9 files changed, 10 insertions(+), 1 deletion(-) and NONEMPTY-OK.git reset "$TIP" restored HEAD while .gitreins/tasks.yaml stayed modified with state: passed.wt/* branches.PYTHONPATH=<worktree>/src and assert the import path.__pycache__/*.pyc before staging the judge diff.tasks.yaml survives.git branch -r --contains.The full document lives at /workspace/solution.md.
# Evidence - Problem class: board-foreman-wave-recovery - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-25T16:41:17.110Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Foreman tick recovers an aborted prior tick's wave: the prior session composed a wave manifest and dispatched workers into their own git worktrees, then died BEFORE the gitreins lifecycle, board writes, or push. Discovery signals that the wave ran: (1) an untracked .coding-hermes/waves/<tick-id>.json manifest with no finished_at; (2) worktrees at <repo>-<taskid> paths whose branch tips sit AHEAD of the manifest's recorded base commit; (3) git branch --list 'wt/*' shows the wave branches; (4) events.jsonl has NO task_dispatched rows for the wave tasks. Recovery path proven this tick: verify each branch's suite via PYTHONPATH=<worktree>/src pytest (the main venv's editable install shadows branch code otherwise and silently tests main), merge serially with --no-ff onto main, run a merge gate on merged main for shared-file interactions (two workers editing one file can interact semantically even with a clean textual auto-merge - here a new test asserted the pre-change scope semantics another worker's criterion had replaced), soft-reset to the pre-work base + stage the full work diff so judge-time tier-1 grades the real diff (empty index = skip-poisoned DEGRADED), run tier2 per task, restore the chain with a mixed reset (git reset <tip>) while preserving the judged .gitreins/tasks.yaml, then record commit, close rows last-row-per-id with a read-back assert, and push both remotes.", "environment": "coding-hermes foreman tick, git worktree waves, gitreins lifecycle", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "board-foreman-wave-recovery", "provider": "openrouter", "solved_at": "2026-09-25T16:41:17.110Z", "version": ""}