{"record":{"id":"9eb754bc1257c255","repo":"coleam00/Archon","slug":"cannot-abandon-run-with-status-run-status-on","errorCode":null,"errorMessage":"Cannot abandon run with status '${run.status}'. Only running, paused, or failed runs can be abandoned.","messagePattern":"Cannot abandon run with status '(.+?)'\\. Only running, paused, or failed runs can be abandoned\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/operations/workflow-operations.ts","lineNumber":472,"sourceCode":"\n  // Reclaim only when our cancel won the CAS. A miss means another lifecycle\n  // owner now controls the run and its environment.\n  if (cancelled) await reclaimCancelledRunContainer(run);\n  return { run, cancelled, cancelledDescendants, cascadeFailures, blockedParentRunId };\n}\n\n/**\n * Abandon a workflow run (marks it as cancelled).\n *\n * Running, paused, AND failed runs can be abandoned. A `failed` run is terminal\n * per TERMINAL_WORKFLOW_STATUSES but remains resumable, so the user must be able\n * to discard it — hence the inline check here intentionally diverges from that\n * constant and blocks only the two non-resumable terminal states.\n */\nexport async function abandonWorkflow(runId: string): Promise<AbandonWorkflowResult> {\n  const run = await getRunOrThrow(runId, 'operations.workflow_abandon_lookup_failed');\n  if (run.status === 'completed' || run.status === 'cancelled') {\n    throw new Error(\n      `Cannot abandon run with status '${run.status}'. Only running, paused, or failed runs can be abandoned.`\n    );\n  }\n  const result = await cancelRunAndCleanup(run, workflowDb.cancelWorkflowRun);\n  return {\n    run: result.run,\n    cancelled: result.cancelled,\n    cascadeFailures: result.cascadeFailures,\n    blockedParentRunId: result.blockedParentRunId,\n  };\n}\n\nexport interface AbandonConversationRunsResult {\n  /** Runs this call actually took to 'cancelled'. */\n  abandoned: number;\n  /**\n   * First cancelled run that left a parent outside the conversation-scoped\n   * mutation paused blocked-on-child (stranded parent id), or null. The user","sourceCodeStart":454,"sourceCodeEnd":490,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/core/src/operations/workflow-operations.ts#L454-L490","documentation":"abandonWorkflow intentionally diverges from the resumable-status constant: it blocks only the two terminal states 'completed' and 'cancelled', since those runs are already finished and cannot be discarded. Running, paused, and failed runs can be abandoned to cancel them and cascade-cancel descendants.","triggerScenarios":"Calling abandonWorkflow on a run whose status is 'completed' or 'cancelled'.","commonSituations":"A cleanup script sweeping old runs hits already-completed ones; an operator double-abandons a run that was already cancelled; automation retries abandon after a first success.","solutions":["Check status before abandoning: only call abandonWorkflow for 'running', 'paused', or 'failed' runs.","Treat 'completed' runs as permanent history — archive or ignore them rather than discarding.","If you get this on 'cancelled', the abandon already happened; no action needed.","Filter cleanup jobs to non-terminal statuses via the run listing query."],"exampleFix":"// before\nfor (const run of runs) await abandonWorkflow(run.id);\n// after\nfor (const run of runs) {\n  if (run.status !== 'completed' && run.status !== 'cancelled') {\n    await abandonWorkflow(run.id);\n  }\n}","handlingStrategy":"validation","validationCode":"if (run.status === 'completed' || run.status === 'cancelled') skip; else await abandonWorkflow(run.id);","typeGuard":"function isAbandonable(run: WorkflowRun): boolean {\n  return run.status !== 'completed' && run.status !== 'cancelled';\n}","tryCatchPattern":"try { await abandonWorkflow(runId); }\ncatch (e) {\n  if ((e as Error).message.startsWith(\"Cannot abandon run with status\")) {\n    console.info('Run already terminal; nothing to abandon.');\n  } else throw e;\n}","preventionTips":["Filter cleanup/abandon jobs to running, paused, or failed runs only.","Treat completed/cancelled runs as immutable history.","Make abandon idempotent: a second abandon on a cancelled run means the first succeeded."],"tags":["workflow","abandon","state-machine","terminal-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}