{"record":{"id":"454ea5ee5db24808","repo":"coleam00/Archon","slug":"failed-to-cancel-resumable-runs-for-conversation","errorCode":null,"errorMessage":"Failed to cancel resumable runs for conversation: ${err.message}","messagePattern":"Failed to cancel resumable runs for conversation: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/db/workflows.ts","lineNumber":633,"sourceCode":"        [conversationId, conversationId]\n      );\n      if (result.rowCount !== resumable.length) {\n        throw new Error(\n          `Resumable run snapshot changed during reset (expected ${String(resumable.length)}, cancelled ${String(result.rowCount)})`\n        );\n      }\n      for (const run of resumable) {\n        await insertWorkflowEvent(query, {\n          workflow_run_id: run.id,\n          event_type: 'workflow_cancelled',\n        });\n      }\n      return resumable.map(run => normalizeWorkflowRun(run));\n    });\n  } catch (error) {\n    const err = error as Error;\n    getLog().error({ err, conversationId }, 'db.workflow_run_cancel_resumable_for_conv_failed');\n    throw new Error(`Failed to cancel resumable runs for conversation: ${err.message}`);\n  }\n}\n\n/**\n * Find the workflow run currently holding the lock on `workingPath`.\n *\n * The lock is held by any row in `(running, paused)` or `pending` younger\n * than `STALE_PENDING_AGE_MS` (orphaned pre-creates beyond that window are\n * ignored — they're from crashed or resume-replaced dispatches).\n *\n * When called from a dispatch that already pre-created its own row, pass\n * `self` (`id` + `startedAt`) so:\n *   1. Self is never returned.\n *   2. If two dispatches both have rows, the deterministic older-wins\n *      tiebreaker `(started_at, id)` ensures both agree on which is \"first.\"\n *      The newer dispatch sees the older row and aborts; the older dispatch\n *      sees nothing.\n *","sourceCodeStart":615,"sourceCodeEnd":651,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/core/src/db/workflows.ts#L615-L651","documentation":"cancelResumableRunsForConversation runs its SELECT, UPDATE, and event inserts inside a transaction; if anything in that transaction throws — including a DB-level error, not the snapshot mismatch in [242] — it is logged (db.workflow_run_cancel_resumable_for_conv_failed) and rethrown with this message. The transaction is rolled back, so no partial cancellations persist.","triggerScenarios":"Calling cancelResumableRunsForConversation when the DB connection drops mid-transaction, an INSERT into workflow_events violates a constraint, a deadlock occurs with another writer, or permissions prevent the UPDATE on workflow_runs.","commonSituations":"Connection pool closed during shutdown; another transaction holding row locks on the same workflow_runs rows (deadlock); workflow_events schema drift; disk-full on the DB server aborting writes.","solutions":["Inspect the wrapped err.message and the db.workflow_run_cancel_resumable_for_conv_failed log for the real cause.","Retry the operation after transient errors (deadlock, connection reset); the rollback makes it safe.","Check for competing transactions locking the same run rows and serialize the reset.","Verify INSERT permissions on workflow_events and UPDATE permissions on workflow_runs."],"exampleFix":"// before\nawait cancelResumableRunsForConversation(conversationId);\n// after\ntry {\n  await cancelResumableRunsForConversation(conversationId);\n} catch (err) {\n  getLog().error({ err, conversationId }, 'conversation.reset_cancel_failed');\n  throw err;\n}","handlingStrategy":"try-catch","validationCode":"const ping = await pool.query('SELECT 1'); // fail fast if DB is unreachable before opening the transaction","typeGuard":"function isCancelResumableError(err: unknown): err is Error {\n  return err instanceof Error && err.message.startsWith('Failed to cancel resumable runs for conversation:');\n}","tryCatchPattern":"try {\n  await cancelResumableRunsForConversation(conversationId);\n} catch (err) {\n  // Transaction rolled back — no partial cancellation. Inspect err.message for deadlock/constraint cause.\n  getLog().error({ err, conversationId }, 'conversation_reset_failed');\n  throw err;\n}","preventionTips":["Retry on deadlock (SQLSTATE 40P01) or connection-reset messages; rollback makes retries safe.","Avoid long-running transactions on workflow_runs that could deadlock the cancel.","Check INSERT/UPDATE grants for the app's DB role after role changes.","Distinguish this error from [242]: this one is a DB failure, that one is a concurrency guard."],"tags":["database","transaction","workflow-run"],"backgroundTag":"database-query-failed","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}