{"record":{"id":"eaca578f36ba7fda","repo":"paperclipai/paperclip","slug":"runner-prp-command-timeout-command-commandid","errorCode":null,"errorMessage":"runner_prp_command_timeout:${command.commandId}","messagePattern":"runner_prp_command_timeout:(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/realtime/runner-prp-ws.ts","lineNumber":223,"sourceCode":"      while (Date.now() < deadline) {\n        const outcome = binding.authority.commandOutcome(command.commandId);\n        if (!outcome) {\n          throw new Error(`runner_prp_command_missing:${command.commandId}`);\n        }\n        if (outcome.status === \"completed\") return outcome.result;\n        if (outcome.status === \"failed\" || outcome.status === \"rejected\") {\n          const message =\n            outcome.result && typeof outcome.result.message === \"string\"\n              ? outcome.result.message\n              : `runner_prp_command_${outcome.status}:${command.commandId}`;\n          throw new Error(message);\n        }\n        await new Promise<void>((resolve) => {\n          const timer = setTimeout(resolve, 10);\n          timer.unref();\n        });\n      }\n      throw new Error(`runner_prp_command_timeout:${command.commandId}`);\n    })(),\n  };\n}\n\nexport class RunnerPrpRuntimeRequestResolutionError extends Error {\n  constructor(\n    readonly code:\n      | \"runner_prp_authority_not_active\"\n      | \"runtime_request_resolution_conflict\",\n  ) {\n    super(code);\n    this.name = \"RunnerPrpRuntimeRequestResolutionError\";\n  }\n}\n\n/**\n * Queue one turn-bound runtime response on the active durable PRP authority.\n * Identical browser retries reuse the original command; a different answer for","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/realtime/runner-prp-ws.ts#L205-L241","documentation":"queueLiveRunnerPrpCommand queues a control command on the live durable PRP (runner process) authority for a run and polls for its outcome. If the command has not reached a terminal status (completed/failed/rejected) within a hard-coded 30-second deadline, the completion promise rejects with `runner_prp_command_timeout:<commandId>`. This is a deliberate fail-loud signal that the connected runner never acknowledged or finished the command in time.","triggerScenarios":"Calling steer() or nativeControl() on an issue/agent whose runner holds the live PRP authority registration, but the runner process is stalled, disconnected mid-command, blocked on a long synchronous turn, or slow to write the command outcome back to the authority; the outcome record exists (never becomes runner_prp_command_missing) but stays pending past 30s.","commonSituations":"Runner agent wedged in a long-running tool call that ignores control messages; WS connection dropped but registration not yet released; heavily loaded runner host delaying the 10ms-poll loop's observed outcome write; steering during a model stream that never yields.","solutions":["Retry the command with a fresh commandId after confirming the runner is still connected (re-queue; the old commandId is abandoned on timeout).","Check the runner process health/logs for the run (runId from queueLiveRunnerPrpCommand's return) to see whether the command was received at all.","Verify the runner's WS connection to the CONNECT_PATH_PREFIX endpoint is alive; a dead socket means outcomes are never delivered — restart or reconnect the runner.","If the command is inherently long (e.g. a full turn), increase the 30_000ms deadline in server/src/realtime/runner-prp-ws.ts:204 or make the deadline command-type dependent."],"exampleFix":"// before\nconst res = queueLiveRunnerPrpCommand({ companyId, issueId, agentId, type: 'steer', payload });\nawait res.completion; // throws runner_prp_command_timeout after 30s\n// after\nconst res = queueLiveRunnerPrpCommand({ companyId, issueId, agentId, type: 'steer', payload });\ntry {\n  await res.completion;\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('runner_prp_command_timeout:')) {\n    // runner did not finish in 30s: surface to user, optionally re-queue\n    throw new Error(`Steer command timed out; runner may be busy (run ${res.runId})`);\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Pre-check that a live authority exists before queueing\nconst hasLive = currentLiveAuthorities.has(liveAuthorityKey({ companyId, issueId, agentId }));\nif (!hasLive) throw new Error('No live runner authority; command would be a no-op or rely on fallback');","typeGuard":"function isPrpCommandTimeout(e: unknown): e is Error & { commandId: string } {\n  const m = e instanceof Error && /^runner_prp_command_timeout:(.+)$/.exec(e.message);\n  if (m) (e as any).commandId = m[1];\n  return !!m;\n}","tryCatchPattern":"try {\n  await queueLiveRunnerPrpCommand({...}).completion;\n} catch (e) {\n  if (isPrpCommandTimeout(e)) {\n    logger.warn({ commandId: e.commandId }, 'runner PRP command timed out; runner may be stalled');\n    // surface to caller / offer retry\n  } else throw e;\n}","preventionTips":["Confirm the runner WS is connected before issuing steer/nativeControl commands","Avoid steering while the runner is mid long-running tool execution","Alert on runner process liveness and last heartbeat per run","Treat runner_prp_command_timeout as retryable only after verifying runner health"],"tags":["timeout","runner","websocket","control-plane"],"backgroundTag":"request-timeout","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}