workflow-fix: single-flight repo-root sync helper (untracked-collision sweep)
Highlight text on any card (body, plan, or an event) to anchor a comment, or leave a whole-task comment here. Mention @claude to summon a reply.
No comments yet.
Overview / Motivation
Auto-filed by the workflow-fix-on-bug protocol from the 2026-07-02 shared-root divergence incident (PM-chat session, follow-on to the serial-compute audit batch #869-#876).
Goal
Add a single-flight root-sync helper so shared-repo-root reconciliation stops being an ad-hoc race between sessions.
Workflow gap
- Bug observed: on 2026-07-02 the repo root sat 582 local / 226 origin commits diverged for ~3h. Every pull-rebase recovery attempt (this session's and at least two other sessions' retry loops, observed live via pgrep) failed the same ways: (a) "untracked working tree files would be overwritten by checkout" — sessions continuously materialize eval_results/figures artifacts at the root uncommitted while worktree sessions commit the same files to origin (81 collisions in one attempt, 148 in the next, growing between attempts); (b) index.lock contention between concurrent recovery loops ("could not detach HEAD", "fatal: could not reset --hard"); (c) stranded/husk autostashes (three rescue stashes accumulated in one day). Origin/main had also LOST task #811's entire folder while its REGISTRY still pointed at tasks/interpreting/811 — recovered manually via a scratch-worktree merge (pushed as 6c1b3fadf7).
- Why it is a workflow gap: CLAUDE.md § Concurrent repo-root committers prescribes "git pull --rebase=merges --autostash once and re-push" but has no answer for untracked-collision failures, no single-flight coordination (N sessions all retry independently and collide), and no stranded-autostash recovery. The identical-vs-differing sweep that actually unblocks the pull was improvised inline twice today.
- Confidence (emitter): high (live incident; evidence in the 2026-07-02 PM-chat transcript).
Proposed change (candidate diff sketch — refine in planning)
New scripts/sync_repo_root.py (workflow-helper):
- Single-flight via flock on
~/.task-workflow/root-sync.lock(second caller exits 0 "another sync in flight"). - Pre-sweep: enumerate origin-only tree paths colliding with untracked root files; byte-identical → rm (content is in git); differing → move to a dated rescue dir (never delete non-identical data); report counts.
git pull --rebase=merges --autostashwith bounded timeout; on a mid-rebase conflict confined totasks/<status>/<N>lifecycle files, auto-resolve keeping the LATER lifecycle state; anything else → abort cleanly + report (never leave a husk).- Detect + restore stranded autostash entries; verify no MERGE_HEAD / rebase-merge husk remains.
- Push with one rebase-retry.
- CLAUDE.md § Concurrent repo-root committers: point the recovery sentence at the helper; session inline retry loops (the "for i in 1..5: pull" patterns observed today) call the helper instead.
Scope / surfaces
- Primary targets: new
scripts/sync_repo_root.py,CLAUDE.md(§ Concurrent repo-root committers), tests - Consider a watcher pass (autonomous_session_watch.py) invoking it when divergence exceeds a threshold — the plan decides whether that leg ships in v1.
Constraints / invariants
- NEVER a repo-root
git reset --hard/git clean -f(standing ban); never delete non-identical untracked data (rescue dir only); fail loud, no silent husks. scripts/workflow_lint.pydefault run + ruff + tests pass.- This session runs under
EPM_WORKFLOW_FIX_SESSION=1/ aworkflow_fix_target:Provenance line — recursion guard applies.
Provenance
- workflow_fix_target: CLAUDE.md, scripts/sync_repo_root.py
- fingerprint: 0b509d58d95f
target_file: CLAUDE.md, scripts/sync_repo_root.py bug_observed: root pull-rebase recovery repeatedly fails on untracked eval_results/figures collisions materialized by concurrent sessions; multiple sessions race independent recovery loops (index.lock contention, could-not-detach-HEAD, stranded autostashes); local main sat 582/226 diverged for hours on 2026-07-02 why_workflow_gap: the documented recovery (pull --rebase=merges once) has no untracked-collision handling, no single-flight coordination, and no stranded-autostash recovery, so every session improvises and they collide proposed_change: add a single-flight scripts/sync_repo_root.py helper (flock; identical-rm / differing-rescue sweep of untracked collisions; pull --rebase=merges --autostash; push; stranded-autostash restore) and point the CLAUDE.md concurrent-committers recovery and session retry loops at it diff_sketch: |
- scripts/sync_repo_root.py: flock single-flight; pre-sweep untracked
- collisions (identical->rm, differing->rescue dir); bounded pull
- --rebase=merges --autostash; lifecycle-file auto-resolve; husk +
- stranded-autostash detection/repair; push with one retry
- CLAUDE.md: recovery sentence points at the helper confidence: high related_task: n/a (2026-07-02 divergence incident)