Problem
gh-symphony repo status can keep reporting a "Recoverable incomplete turn" after the associated issue workspace has already been removed.
Observed local state:
status.json still contains recovery.kind = "incomplete-turn-dirty-workspace".
- The recovery
workspacePath points to a deleted repository directory.
- The run record is still preserved with
status = "suppressed" and recovery != null.
- The issue workspace record has
status = "removed" and its repositoryPath no longer exists.
gh-symphony repo recover --dry-run --json returns [], so the status output advertises a recovery that the recovery command cannot act on.
This makes repo status look actionable even though the recovery context is stale.
Root cause
This appears to be an Observability Layer bug crossing the Execution Layer workspace lifecycle boundary:
cleanupTerminalIssueWorkspace() removes the issue workspace and marks the workspace record as removed, while intentionally preserving run records.
buildProjectSnapshot() derives snapshot.recovery from allRuns only.
findLatestRecovery() suppresses stale recovery after a later recovery run completes, but it does not know whether the workspace backing the recovery has since been removed.
repo status renders snapshot.recovery without validating that the workspace still exists or is still active.
Proposed fix
Prefer keeping run records immutable/preserved, and make the status snapshot smarter:
- Pass issue workspace records into
buildProjectSnapshot() or otherwise provide a resolver for recovery workspace state.
- Treat an
incomplete-turn-dirty-workspace recovery as resolved/stale when its issue workspace record is removed.
- Optionally also suppress the recovery when the recorded recovery
workspacePath no longer exists.
- Keep the run record intact for audit/history.
This keeps the cleanup behavior intact while making the Observability Layer report only currently actionable recovery.
Expected behavior
After a terminal cleanup removes an issue workspace:
gh-symphony repo status should not show "Recoverable incomplete turn" for that removed workspace.
status.json.recovery should be null.
- Historical run records may still preserve their
recovery payload.
repo explain should not warn that dispatch will start a recovery turn for a removed workspace.
Test cases
- Unit:
buildProjectSnapshot() does not surface recovery when the matching workspace record has status: "removed".
- Unit:
buildProjectSnapshot() still surfaces recovery when the workspace record is active and dirty recovery is unresolved.
- Orchestrator/service: create a suppressed incomplete-turn dirty-workspace run, then run terminal workspace cleanup; the saved project status should have
recovery: null.
- CLI:
repo status does not render the "Recoverable incomplete turn" block when snapshot recovery is null.
Notes
This should not modify docs/symphony-spec.md. The change aligns status reporting with the existing workspace lifecycle instead of changing the upstream spec.
Problem
gh-symphony repo statuscan keep reporting a "Recoverable incomplete turn" after the associated issue workspace has already been removed.Observed local state:
status.jsonstill containsrecovery.kind = "incomplete-turn-dirty-workspace".workspacePathpoints to a deleted repository directory.status = "suppressed"andrecovery != null.status = "removed"and itsrepositoryPathno longer exists.gh-symphony repo recover --dry-run --jsonreturns[], so the status output advertises a recovery that the recovery command cannot act on.This makes
repo statuslook actionable even though the recovery context is stale.Root cause
This appears to be an Observability Layer bug crossing the Execution Layer workspace lifecycle boundary:
cleanupTerminalIssueWorkspace()removes the issue workspace and marks the workspace record asremoved, while intentionally preserving run records.buildProjectSnapshot()derivessnapshot.recoveryfromallRunsonly.findLatestRecovery()suppresses stale recovery after a later recovery run completes, but it does not know whether the workspace backing the recovery has since been removed.repo statusrenderssnapshot.recoverywithout validating that the workspace still exists or is still active.Proposed fix
Prefer keeping run records immutable/preserved, and make the status snapshot smarter:
buildProjectSnapshot()or otherwise provide a resolver for recovery workspace state.incomplete-turn-dirty-workspacerecovery as resolved/stale when its issue workspace record isremoved.workspacePathno longer exists.This keeps the cleanup behavior intact while making the Observability Layer report only currently actionable recovery.
Expected behavior
After a terminal cleanup removes an issue workspace:
gh-symphony repo statusshould not show "Recoverable incomplete turn" for that removed workspace.status.json.recoveryshould benull.recoverypayload.repo explainshould not warn that dispatch will start a recovery turn for a removed workspace.Test cases
buildProjectSnapshot()does not surface recovery when the matching workspace record hasstatus: "removed".buildProjectSnapshot()still surfaces recovery when the workspace record is active and dirty recovery is unresolved.recovery: null.repo statusdoes not render the "Recoverable incomplete turn" block when snapshot recovery is null.Notes
This should not modify
docs/symphony-spec.md. The change aligns status reporting with the existing workspace lifecycle instead of changing the upstream spec.