{"record":{"id":"5069455cd9db7d9a","repo":"coleam00/Archon","slug":"failed-to-cancel-workflow-run-err-message","errorCode":null,"errorMessage":"Failed to cancel workflow run: ${err.message}","messagePattern":"Failed to cancel workflow run: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/db/workflows.ts","lineNumber":1340,"sourceCode":"        `UPDATE remote_agent_workflow_runs\n         SET status = 'cancelled', completed_at = ${dialect.now()}\n         WHERE id = $1 AND status NOT IN ('completed', 'cancelled')`,\n        [id]\n      );\n      if ((update.rowCount ?? 0) > 0) {\n        await insertWorkflowEvent(query, {\n          workflow_run_id: id,\n          event_type: 'workflow_cancelled',\n          step_name: event?.step_name,\n          data: event?.reason === undefined ? undefined : { reason: event.reason },\n        });\n      }\n      return update;\n    });\n  } catch (error) {\n    const err = error as Error;\n    getLog().error({ err }, 'db.workflow_run_cancel_failed');\n    throw new Error(`Failed to cancel workflow run: ${err.message}`);\n  }\n  const cancelled = (result.rowCount ?? 0) > 0;\n  if (!cancelled) {\n    // Idempotent no-op: the run was already terminal. Returned so callers can\n    // report \"nothing to cancel\" instead of a false \"Cancelled\" (see #1830 I1).\n    // Same info level as the resume CAS-miss signal for consistency (S2).\n    getLog().info({ workflowRunId: id }, 'db.workflow_run_cancel_noop');\n  }\n  return { cancelled };\n}\n\nexport async function cancelFanOutRun(\n  id: string,\n  reason: FanOutCancelReason\n): Promise<{ cancelled: boolean }> {\n  const dialect = getDialect();\n  let result: Awaited<ReturnType<IDatabase['query']>>;\n  try {","sourceCodeStart":1322,"sourceCodeEnd":1358,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/core/src/db/workflows.ts#L1322-L1358","documentation":"cancelWorkflowRun() sets status='cancelled' unless the run is already 'completed' or 'cancelled' (a 'failed' run remains cancellable). This wrapper rethrows database errors from the transaction with the original message appended. A zero-row update is NOT an error here — it returns { cancelled: false } as an idempotent no-op.","triggerScenarios":"Calling cancelWorkflowRun(id, event?) when the UPDATE or the workflow_cancelled event insert throws: DB connection failure, constraint violation on the event insert, dialect JSON/now() failure, or transaction rollback/deadlock.","commonSituations":"Database unreachable or restarted while an operator or executor issues a cancel; workflow_events table missing a newly added column (schema drift between binary and database); long-running transaction contention on the run row; passing an event detail object with an unexpected shape that breaks the insert.","solutions":["Check the appended underlying message and the 'db.workflow_run_cancel_failed' log for the root cause","Verify database connectivity, then reissue the cancel — it is safe to retry (terminal rows are guarded, repeat cancels return cancelled:false)","Confirm the database schema matches the binary version (apply migrations)","If event details were passed, ensure step_name/reason are simple strings matching WorkflowCancellationEventDetails","Use the returned { cancelled } flag rather than assuming the run stopped; the executor honors cancellation cooperatively"],"exampleFix":"// before\nawait cancelWorkflowRun(id, { step_name: 'deploy', reason: 42 } as any);\n// after\nconst { cancelled } = await cancelWorkflowRun(id, { step_name: 'deploy', reason: 'user requested' });\nif (!cancelled) getLog().info({ runId: id }, 'run already terminal; nothing to cancel');","handlingStrategy":"try-catch","validationCode":"const run = await getWorkflowRun(id);\nif (!run) throw new Error(`run ${id} does not exist`);\nif (['completed', 'cancelled'].includes(run.status)) {\n  return { cancelled: false }; // cancel would be a no-op anyway\n}","typeGuard":"function isCancellationEvent(v: unknown): v is WorkflowCancellationEventDetails {\n  const e = v as WorkflowCancellationEventDetails;\n  return (e.step_name === undefined || typeof e.step_name === 'string') &&\n         (e.reason === undefined || typeof e.reason === 'string');\n}","tryCatchPattern":"try {\n  const { cancelled } = await cancelWorkflowRun(id, event);\n  if (!cancelled) getLog().info({ runId: id }, 'nothing to cancel');\n} catch (err) {\n  getLog().error({ err, runId: id }, 'cancel db error');\n  await backoffThenRetry(() => cancelWorkflowRun(id)); // retry is safe: terminal rows are guarded\n}","preventionTips":["Rely on the { cancelled } return value instead of assuming success","Retry cancels safely — the guard makes repeat calls idempotent","Keep schema/migrations current so the workflow_events insert cannot fail","Pass only string step_name/reason values in event details","Remember 'failed' runs are still cancellable; 'completed'/'cancelled' are not"],"tags":["database","workflow","cancellation"],"backgroundTag":"workflow-state-update-failed","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}