workflow-fix: audit duplicate-id dirs + reap terminal-task status husks
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 an orchestrator-observed bug during a fleet progress check (2026-07-16, chat session; emitting agent: orchestrator).
Goal
task.py audit detects duplicate tasks/<status>/<id> dirs for a single id, and a sweep reaps merge-reintroduced stale-status husk dirs of TERMINAL tasks (husk contents verified as a subset/prefix of the live folder before removal; any husk with unique content is escalated, never deleted).
Workflow gap
- Bug observed:
ls tasks/running/ls tasks/reviewinglist tasks #721, #1107, #1227 although all three are atcompletedper REGISTRY — each id has TWO on-disk dirs (e.g.tasks/reviewing/1107/ANDtasks/completed/1107/), the husk holding stale-prefix copies ofevents.jsonl(31 vs 36 lines) /concerns.jsonl/plans//artifacts/.task.py auditreportsAUDIT PASS — registry and filesystem agreedespite the duplicates, and the husks are git-TRACKED (cleangit status), so they persist indefinitely. - Why it is a workflow gap: the #644 ghost-aware staging in
task_workflow.pyset_status(the_task_status_dir_pathspecsblock, ~L4175-4195) sweeps only dirs "tracked in HEAD but ABSENT on disk" at the NEXT transition of the same task. This husk class is the inverse and unreachable by that sweep:git log --oneline -2 -- tasks/reviewing/1107/shows the husk was RE-INTRODUCED by a concurrent branch's later merge (117e4e3fa5, issue-1108 PR #872) AFTER the move commit (f73e033488,task #1107: reviewing → completed) — the branch was cut before the move and its rebase-merge re-added the old-path files. Terminal tasks have no further transitions, so nothing ever sweeps the husk;auditonly checks registry→path agreement, not path uniqueness per id. Consequence: anyls tasks/<status>-based fleet read (progress checks, dashboards, ad-hoc triage) misreports terminal tasks as active. - Confidence (emitter): high
- verified-at-filing:
ls -d tasks/*/721 tasks/*/1107 tasks/*/1227→ 2 dirs per id (6 total:running/721+completed/721,reviewing/1107+completed/1107,reviewing/1227+completed/1227);uv run python scripts/task.py audit→ PASS despite them;wc -lhusk-vs-live: 1107 events 31/36 (stale prefix, last husk marker 2026-07-07T12:12 < live 12:35), 1227 events 17/23, concerns 3/3 identical;git log --oneline -2 -- tasks/reviewing/1107/→ husk re-created by117e4e3fa5post-move (2026-07-16). Absence-of-guard claim for the audit check + terminal-husk sweep: context ofsrc/explore_persona_space/task_workflow.py~L4175-4195 READ — the ghost pathspec helper covers tracked-in-HEAD-absent-on-disk only (the comment says so explicitly); no duplicate-id-dir check exists in theauditpath ofscripts/task.py(grepduplicateinscripts/task.py→ 2 hits, both unrelated: re-post marker warning L595, duplicate-frontmatter trap L1387).
Proposed change (candidate diff sketch — refine in planning)
+ task.py audit: after the registry→path pass, glob tasks/*/<id> per known id;
+ >1 hit => report DUPLICATE-DIR (id, live path per REGISTRY, husk paths) => audit FAIL (or WARN tier).
+ task_workflow.py: reap_stale_status_husks(id|--all): for TERMINAL (completed/archived) ids with
+ duplicate dirs, verify husk events.jsonl is a line-prefix/subset of the live one (+ no file present
+ only in the husk with unique content); if verified => git rm -r the husk + commit by explicit path;
+ else => escalate (sidecar row + loud report), never delete.
+ Wire the reap into an existing janitor pass (worktree_audit or the watcher's orphan sweep) or expose
+ via task.py; also consider a set_status-time sweep of OTHER-id husks matching the moved id's pattern.
Scope / surfaces
- Primary target:
src/explore_persona_space/task_workflow.py,scripts/task.py - Grep the workflow surface for the pattern before editing (
grep -rn 'ghost\|_task_status_dir_pathspecs' src/explore_persona_space/task_workflow.py scripts/ .claude/) and update every hit; list them in the plan.
Constraints / invariants
- Workflow-surface only — never experiment code,
configs/, ortasks/state edits outside the husk reap itself. scripts/workflow_lint.py --check-askspasses; ruff on touched files passes; tests pinning task-workflow invariants updated/added (tests/test_task_workflow*.py).- Never delete a husk with unique content (a marker present only in the husk is evidence of a lost concurrent write — escalate).
- This session runs under
EPM_WORKFLOW_FIX_SESSION=1and carries aworkflow_fix_target:Provenance line — it MUST NOT auto-route any of its own subagents' workflow-fix candidates (recursion guard).
Provenance
- workflow_fix_target: src/explore_persona_space/task_workflow.py, scripts/task.py
- fingerprint: bb84ac5c4962
target_file: src/explore_persona_space/task_workflow.py, scripts/task.py bug_observed: Terminal tasks #721/#1107/#1227 each have a second, git-tracked stale-status dir (e.g. tasks/reviewing/1107/ beside tasks/completed/1107/) re-introduced by a concurrent branch merge after the status move; task.py audit PASSes despite the duplicates and nothing ever sweeps them, so ls-based fleet reads misreport terminal tasks as active. why_workflow_gap: The #644 ghost-aware staging sweeps only tracked-in-HEAD-absent-on-disk dirs at the task's NEXT transition — merge-reintroduced on-disk husks of TERMINAL tasks are the inverse case and terminal tasks have no next transition; audit checks registry→path agreement but not path uniqueness per id. proposed_change: audit flags duplicate tasks// dirs per id; a subset-verified reap removes terminal-task husks (escalating, never deleting, on unique husk content). diff_sketch: |
- audit: glob tasks/*/ per id; >1 => DUPLICATE-DIR finding => FAIL/WARN
- reap_stale_status_husks: terminal ids only; husk events.jsonl must be prefix/subset of live; git rm + explicit-path commit; else escalate confidence: high related_task: n/a (surfaced during chat-session fleet check)