{"record":{"id":"e2e2fbffc28bccb5","repo":"coleam00/Archon","slug":"failed-to-complete-workflow-run-err-message","errorCode":null,"errorMessage":"Failed to complete workflow run: ${err.message}","messagePattern":"Failed to complete workflow run: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/db/workflows.ts","lineNumber":1227,"sourceCode":"        : await query(\n            `UPDATE remote_agent_workflow_runs\n             SET status = 'completed', completed_at = ${dialect.now()}\n             WHERE id = $1 AND status = 'running'`,\n            [id]\n          );\n      if ((update.rowCount ?? 0) > 0) {\n        await insertWorkflowEvent(query, {\n          workflow_run_id: id,\n          event_type: 'workflow_completed',\n          data: completion,\n        });\n      }\n      return update;\n    });\n  } catch (error) {\n    const err = error as Error;\n    getLog().error({ err }, 'db.workflow_run_complete_failed');\n    throw new Error(`Failed to complete workflow run: ${err.message}`);\n  }\n  if (result.rowCount === 0) {\n    getLog().warn({ workflowRunId: id }, 'db.workflow_run_complete_no_match');\n    throw new Error(`Workflow run not found or not in running state (id: ${id})`);\n  }\n}\n\n/**\n * Mark a run failed.\n *\n * Matches `pending` as well as `running`. A run can fail BEFORE it ever transitions to\n * running — source capture, artifact setup, and credential resolution all happen against\n * a freshly inserted `pending` row — and a `running`-only guard left those rows pending\n * forever: no terminal state, no error recorded, and nothing to tell the operator the run\n * is dead. Both are non-terminal states owned by this process, so failing either is the\n * same decision. Terminal rows still never transition.\n */\nexport async function failWorkflowRun(","sourceCodeStart":1209,"sourceCodeEnd":1245,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/core/src/db/workflows.ts#L1209-L1245","documentation":"completeWorkflowRun() marks a workflow run row as 'completed' inside a transaction (and inserts a workflow_completed event). This wrapper error rethrows any failure from that transaction — connection loss, constraint violation, dialect/JSON merge error, transaction abort — with the original DB message appended. It distinguishes a database-level failure from the 'no matching running row' case (error 261).","triggerScenarios":"Calling completeWorkflowRun(id, completion, metadata?) when the underlying UPDATE/INSERT transaction fails: database unreachable or dropped mid-transaction, metadata JSON serialization/dialect jsonMerge failure, workflow event insert constraint violation, or transaction deadlock/rollback.","commonSituations":"Postgres/SQLite restarted or connection pooled out during a long workflow; passing metadata containing values that fail JSON.stringify or violate a schema; a duplicate-key error on the workflow event insert; running against a stale database whose remote_agent_workflow_runs or events table lacks expected columns.","solutions":["Inspect the appended original message (err.message) and the 'db.workflow_run_complete_failed' log for the underlying DB error","Verify database connectivity and that the DB server was up for the duration of the run","Ensure the metadata argument is JSON-serializable and matches the expected Record<string, unknown> shape","Confirm the schema is up to date (migrations applied; both SQLite and PostgreSQL shapes aligned)","Retry the completion write once the database is healthy; the run must still be in 'running' state for the update to apply"],"exampleFix":"// before\nawait completeWorkflowRun(id, { duration_ms: 1234 }, metadata as any);\n// after\ntry {\n  await completeWorkflowRun(id, { duration_ms: 1234 }, metadata);\n} catch (err) {\n  if (!err.message.startsWith('Workflow run not found')) {\n    getLog().error({ err, workflowRunId: id }, 'complete retryable db failure');\n    await completeWorkflowRun(id, { duration_ms: 1234 }); // retry without metadata\n  }\n}","handlingStrategy":"try-catch","validationCode":"const run = await getWorkflowRun(id);\nif (!run) throw new Error(`run ${id} does not exist`);\nif (run.status !== 'running') throw new Error(`run ${id} is ${run.status}, not running`);\nJSON.stringify(metadata ?? {}); // ensure metadata is serializable","typeGuard":"function isRecord(v: unknown): v is Record<string, unknown> {\n  return typeof v === 'object' && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  await completeWorkflowRun(id, { duration_ms }, metadata);\n} catch (err) {\n  if (err.message.startsWith('Workflow run not found')) {\n    // state issue: reconcile with current run status\n  } else {\n    // DB failure: log err.message (original cause) and retry after health check\n  }\n}","preventionTips":["Confirm run status is 'running' before completing","Keep metadata JSON-serializable and schema-conformant","Keep schema/migrations current for the deployed binary","Monitor 'db.workflow_run_complete_failed' logs for the underlying DB error","Avoid calling completion from multiple code paths"],"tags":["database","workflow","state-transition"],"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"}