{"record":{"id":"b26e50694cc82d24","repo":"coleam00/Archon","slug":"detached-workflow-owner-has-no-resolved-run-id","errorCode":null,"errorMessage":"Detached workflow owner has no resolved run ID","messagePattern":"Detached workflow owner has no resolved run ID","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/commands/workflow.ts","lineNumber":2810,"sourceCode":"  //\n  // Guard rails (#1123): a signal must only ever fail THE run this process is\n  // driving, and only while that run is still 'running'. The run id is learned\n  // from the resumable lookup (resume path), the row a detached parent handed\n  // this child (#2872), or the workflow_started emitter event (fresh runs, see\n  // the subscription below) — never from a\n  // conversation-wide \"active run\" query, which can match a run driven by\n  // another process (children share parent_conversation_id). When the run has\n  // already transitioned elsewhere — paused at a gate, completed, cancelled —\n  // the handler leaves it alone; see \"No Autonomous Lifecycle Mutation Across\n  // Process Boundaries\" in CLAUDE.md. The handlers themselves are removed in\n  // the finally below once executeWorkflow returns, so a late signal can never\n  // touch a settled run (and repeated workflowRunCommand calls in one process\n  // don't stack handlers).\n  let ownedRunId: string | undefined = resumable?.id ?? detachedPreCreatedRun?.id;\n  let detachedRunControl: Awaited<ReturnType<typeof startDetachedRunControlServer>> | undefined;\n  if (detachedProcessOwner) {\n    if (ownedRunId === undefined) {\n      throw new Error('Detached workflow owner has no resolved run ID');\n    }\n    detachedRunControl = await startDetachedRunControlServer(ownedRunId);\n  }\n  let terminating = false;\n  const cleanup = (signal: string): void => {\n    if (terminating) return;\n    terminating = true;\n    getLog().info({ conversationId: conversation.id, signal }, 'workflow.process_terminating');\n    const interruptedRunId = ownedRunId;\n    (async (): Promise<void> => {\n      if (!interruptedRunId) {\n        // Signal before this process created/resumed a run — nothing it owns.\n        // A pre-created 'pending' row is covered by the stale-pending hygiene.\n        getLog().info(\n          { conversationId: conversation.id, signal },\n          'workflow.termination_no_owned_run'\n        );\n        return;","sourceCodeStart":2792,"sourceCodeEnd":2828,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/cli/src/commands/workflow.ts#L2792-L2828","documentation":"In detached mode, the owner process must have a run ID to start the run-control server; `ownedRunId` comes from `resumable?.id ?? detachedPreCreatedRun?.id`. If the process is the detached owner but neither source yielded an ID, the CLI throws to avoid starting an uncontrolled detached run.","triggerScenarios":"Detached execution is requested, but run pre-creation failed silently or resume resolution returned no run, so `ownedRunId === undefined` when `startDetachedRunControlServer` is called.","commonSituations":"A partial failure in run pre-creation (DB write failed upstream without surfacing); combining detach flags with resume context inconsistently through a wrapper; an interrupted first detach where the run row was never committed.","solutions":["Re-run the workflow command; the run should pre-create cleanly this time.","Check logs preceding this throw for a swallowed run-creation/DB error.","Do not combine detach flags with resume flags unless a resumable run exists (`archon workflow list`).","If reproducible, file a bug — this is an invariant violation (owner process without a run record)."],"exampleFix":"// before\nif (ownedRunId === undefined) {\n  throw new Error('Detached workflow owner has no resolved run ID');\n}\n// after\nif (ownedRunId === undefined) {\n  throw new Error(`Detached workflow owner has no resolved run ID (workflow=${workflowName})`);\n}","handlingStrategy":"type-guard","validationCode":"// before starting detached control, ensure a run id resolves\nconst ownedRunId: string | undefined = resumable?.id ?? detachedPreCreatedRun?.id;\nif (detachedProcessOwner && typeof ownedRunId !== 'string') {\n  throw new Error('Refusing to detach: no run record exists to own');\n}","typeGuard":"function hasResolvedRunId(r: { id?: string } | undefined): r is { id: string } {\n  return typeof r?.id === 'string' && r.id.length > 0;\n}","tryCatchPattern":"try {\n  detachedRunControl = await startDetachedRunControlServer(ownedRunId as string);\n} catch (err) {\n  getLog().error({ err, ownedRunId }, 'cli.detached_control_start_failed');\n  throw err;\n}","preventionTips":["Confirm run pre-creation succeeded (check logs) before detaching.","Do not mix detach and resume flags unless a resumable run exists.","List runs (`archon workflow list`) to verify the run record exists before detaching.","Treat this error as a bug report trigger — it signals a broken invariant."],"tags":["detach","state","invariant"],"backgroundTag":"missing-run-state","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}