{"record":{"id":"3b9718c0ec3821eb","repo":"coleam00/Archon","slug":"workflow-run-not-found-or-not-in-running-state-id","errorCode":null,"errorMessage":"Workflow run not found or not in running state (id: ${id})","messagePattern":"Workflow run not found or not in running state \\(id: (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/db/workflows.ts","lineNumber":1231,"sourceCode":"            [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(\n  id: string,\n  error: string,\n  scheduledResume?: ScheduledWorkflowResume\n): Promise<void> {","sourceCodeStart":1213,"sourceCodeEnd":1249,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/core/src/db/workflows.ts#L1213-L1249","documentation":"completeWorkflowRun() guards its UPDATE with 'WHERE id = $1 AND status = 'running''. If rowCount is 0 the run either does not exist or is not currently running, so the library refuses to stamp completion — protecting terminal state from being overwritten. Unlike cancel, this is a thrown error, not an idempotent no-op.","triggerScenarios":"Calling completeWorkflowRun(id, ...) for: an id that was never inserted; a run already 'completed', 'failed', or 'cancelled'; a run still 'pending' (never transitioned to running); or a concurrent terminal transition that won a race.","commonSituations":"Caller completed the run twice (double completion in executor and a finally block); run failed earlier so failWorkflowRun already set terminal state; operator cancelled the run while it was finishing; typo'd or foreign run id; resumed run rows replaced with new ids.","solutions":["Fetch the run by id and check its status before completing","Only call completeWorkflowRun for runs confirmed in 'running' state","If the run is already terminal, treat completion as done (idempotent) instead of retrying","If the run is 'pending', first transition it to running per the executor lifecycle, or use failWorkflowRun (which also matches pending)","Verify the id is the correct workflow run id (not a step/event id)"],"exampleFix":"// before\nawait completeWorkflowRun(runId, { duration_ms });\n// after\nconst run = await getWorkflowRun(runId);\nif (run?.status === 'running') {\n  await completeWorkflowRun(runId, { duration_ms });\n} else {\n  getLog().warn({ runId, status: run?.status }, 'skip complete: not running');\n}","handlingStrategy":"validation","validationCode":"const run = await getWorkflowRun(id);\nif (!run || run.status !== 'running') {\n  throw new Error(`cannot complete run ${id}: status=${run?.status ?? 'missing'}`);\n}","typeGuard":"function isRunning(run: { status: string } | undefined | null): boolean {\n  return run?.status === 'running';\n}","tryCatchPattern":"try {\n  await completeWorkflowRun(id, { duration_ms });\n} catch (err) {\n  if (err.message.includes('not found or not in running state')) {\n    const run = await getWorkflowRun(id);\n    getLog().info({ id, status: run?.status }, 'completion skipped: run not running');\n  } else { throw err; }\n}","preventionTips":["Check status before terminal transitions","Make completion idempotent at the call site (skip if already terminal)","Avoid racing executor completion against cancel/fail paths","Validate run ids come from the same database they are written to","Watch for 'db.workflow_run_complete_no_match' warnings as a signal of double completion"],"tags":["database","workflow","state-transition","race-condition"],"backgroundTag":"workflow-run-not-running","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}