{"record":{"id":"3d1ce56af9692949","repo":"coleam00/Archon","slug":"workflow-run-run-id-was-already-string-approv","errorCode":null,"errorMessage":"Workflow run ${run.id} was already ${String(approval.resolved)} and is awaiting resume.","messagePattern":"Workflow run (.+?) was already (.+?) and is awaiting resume\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/core/src/operations/workflow-operations.ts","lineNumber":363,"sourceCode":"      // Same redirect as assertApprovable: the parent's pause is not a rejectable\n      // gate — cancelling the parent here would silently orphan the still-paused\n      // child run. Reject the child (its own gate) or abandon the parent (which\n      // cascade-cancels the subtree) instead.\n      throw new Error(\n        `Run ${run.id} is paused waiting on sub-run ${attention.childRunId} ` +\n          `('workflow:' node '${attention.nodeId}'). Reject the child run instead` +\n          `: /workflow reject ${attention.childRunId}` +\n          ' To discard the whole tree, abandon this run.'\n      );\n    case 'unreadable':\n      // The one deliberate divergence from approve: unreadable gate METADATA is\n      // still rejectable (see this function's doc comment). An unrecognized gate\n      // TYPE is not, and neither is a block pointer with nothing to follow.\n      if (attention.reason === 'malformed_gate') break;\n      throw new Error(unreadableGateMessage(run, attention, approval));\n    case undefined:\n      if (approval && isGateResolved(approval)) {\n        throw new Error(\n          `Workflow run ${run.id} was already ${String(approval.resolved)} and is awaiting resume.`\n        );\n      }\n      break;\n    case 'awaiting_response':\n    case 'terminal':\n      // 'terminal' is unreachable: the status guard above already returned for it.\n      break;\n  }\n  return approval;\n}\n\n// ---------------------------------------------------------------------------\n// Operations\n// ---------------------------------------------------------------------------\n\n/**\n * List all running and paused workflow runs.","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/core/src/operations/workflow-operations.ts#L345-L381","documentation":"When the run has no attention block pointer but its approval metadata already carries a resolved decision (isGateResolved), the gate was already decided and the run is merely waiting for the engine to resume it. Rejecting again would double-resolve the gate, so the library throws.","triggerScenarios":"Calling workflowRejectCommand on a paused run where attention is undefined and approval.metadata passes isGateResolved — i.e. approve/reject already committed but the resume hasn't executed yet.","commonSituations":"Double-clicking or re-sending a reject command; a bot retries a rejected command after a timeout while the resume is still queued; UI state not refreshed after the first successful decision.","solutions":["Do nothing — the gate is already resolved; wait for the run to resume on its own.","If the run stays paused indefinitely, resume it manually with the resume command or investigate the resume worker.","Debounce/deduplicate decision commands per run ID in your UI or bot layer."],"exampleFix":"// before\nawait workflowRejectCommand({ runId });\n// after\nif (!isGateResolved(run.metadata.approval)) {\n  await workflowRejectCommand({ runId });\n} // else: already decided, awaiting resume","handlingStrategy":"type-guard","validationCode":"if (isGateResolved(run.metadata?.approval)) return; // already decided, awaiting resume","typeGuard":"function isGateResolved(a: unknown): boolean {\n  return !!a && typeof a === 'object' && 'resolved' in a && (a as any).resolved != null;\n}","tryCatchPattern":"try { await workflowRejectCommand({ runId }); }\ncatch (e) {\n  if ((e as Error).message.includes('already') && (e as Error).message.includes('awaiting resume')) return; // no-op\n  throw e;\n}","preventionTips":["Debounce/deduplicate decision commands per run ID.","Disable decision buttons once approval.resolved is set.","Poll run state after a decision instead of issuing repeated commands."],"tags":["workflow","approval-gate","idempotency","duplicate-request"],"backgroundTag":"gate-already-resolved","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}