Auto-reconcile merged PRs and orphaned in-progress tasks in task manager
Problem
Extend task-manager cleanup so heartbeat/reconcile can automatically mark tasks completed when their PR is merged, demote orphaned in_progress tasks that have no register/worktree/live dispatch, and clear stale dispatch/register state when runtime has already handed off to PR lifecycle.
Context
scripts/task-manager-heartbeatalready callsreconcile_register_orphans,detect_metadata_drift, anddetect_dispatch_completed_unreconciledon every cycle.- Those hooks are not enough because only
reconcile_register_orphansmutates state, and it only prunes a narrow case: terminal task plus missing worktree, or missing task plus missing worktree. - Current failures in live manager state:
- merged PR task files can remain
in_progressindefinitely - tasks can remain
in_progresswith no register entry, no worktree, and no live dispatch - stale dispatch status files linger after successful handoff to PR lifecycle
- Relevant files:
scripts/task-manager-heartbeatscripts/task_manager_lib.pytests/scripts/test_task_manager_scripts.py- Constraint: automatic repair should only handle high-confidence cases and should not silently rewrite genuinely active tasks.
Possible Solutions
- Option 1: Keep the current model and only improve attention text. Low risk, but it preserves manual cleanup and leaves heartbeat noisy. Rejected.
- Option 2: Recommended Add explicit auto-reconcile helpers for high-confidence cases:
- merged PR => mark task
completed in_progresswith no register/worktree/live dispatch and no recent activity => demote out of active state- dispatch
exitedplus PR/terminal task => clear stale runtime residue This keeps repair logic narrow and testable while removing the most common false-stall cases. - Option 3: Build a broad self-healing loop that edits any suspicious task state heuristically. Faster cleanup, but too risky without stronger invariants.
Plan
- Add explicit reconciliation helpers in
scripts/task_manager_lib.pyfor: - merged-PR completion - orphanedin_progressdemotion - stale dispatch/register cleanup after handoff - Call those helpers from
scripts/task-manager-heartbeatafter CI/merge refresh and before summary rendering. - Add focused tests in
tests/scripts/test_task_manager_scripts.pycovering both repair and non-repair cases. - Update task-manager docs/skill if the repair rules change operator expectations.
Implementation Progress
Added three auto-reconcile helpers to scripts/task_manager_lib.py:
-
auto_complete_merged_pr_tasks(tasks)— marks non-terminal taskscompletedwhentask.pr_state == "MERGED". Updates only thestatusline in frontmatter via regex to preserve all other fields. -
auto_demote_orphaned_in_progress(tasks, *, stale_seconds=DEFAULT_IDLE_SECONDS)— resetsin_progresstasks toreadywhen: not registered, no existing worktree directory, dispatch not running, and last update > stale_seconds (1200s default). Covers tasks stuckin_progresswith no active worker. -
auto_clear_stale_dispatch_state(tasks)— removes.tasks/dispatch-{slug}.logand.tasks/dispatch-{slug}.status.jsonwhen dispatch exited cleanly and the task has a PR number (handed off to PR lifecycle) or is terminal.
Private helper _set_task_status(path, new_status) rewrites only the status:
line in frontmatter using re.sub, preserving all other fields and body.
scripts/task-manager-heartbeat calls all three after refresh_pr_ci_for_tasks.
If any task files were mutated, tasks are re-collected and PR CI re-fetched before
classification.
Added 13 focused tests covering repair and non-repair cases for all three helpers.
Updated one existing integration test that expected a stale orphaned in_progress
task to produce a stuck_in_progress escalation — with auto-demotion active, that
task is now reset to ready before classification runs.
QA Exploration
- [x] QA exploration completed (or N/A for non-UI tasks)
- N/A: backend task-manager behavior only
Review Feedback
- [x] Review cleared