paperclipai/paperclip · error · Error
runner_prp_command_timeout:${command.commandId}
Error message
runner_prp_command_timeout:${command.commandId} What it means
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.
Source
Thrown at server/src/realtime/runner-prp-ws.ts:223
while (Date.now() < deadline) {
const outcome = binding.authority.commandOutcome(command.commandId);
if (!outcome) {
throw new Error(`runner_prp_command_missing:${command.commandId}`);
}
if (outcome.status === "completed") return outcome.result;
if (outcome.status === "failed" || outcome.status === "rejected") {
const message =
outcome.result && typeof outcome.result.message === "string"
? outcome.result.message
: `runner_prp_command_${outcome.status}:${command.commandId}`;
throw new Error(message);
}
await new Promise<void>((resolve) => {
const timer = setTimeout(resolve, 10);
timer.unref();
});
}
throw new Error(`runner_prp_command_timeout:${command.commandId}`);
})(),
};
}
export class RunnerPrpRuntimeRequestResolutionError extends Error {
constructor(
readonly code:
| "runner_prp_authority_not_active"
| "runtime_request_resolution_conflict",
) {
super(code);
this.name = "RunnerPrpRuntimeRequestResolutionError";
}
}
/**
* Queue one turn-bound runtime response on the active durable PRP authority.
* Identical browser retries reuse the original command; a different answer forView on GitHub (pinned to 01ad858492)
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.
Example fix
// before
const res = queueLiveRunnerPrpCommand({ companyId, issueId, agentId, type: 'steer', payload });
await res.completion; // throws runner_prp_command_timeout after 30s
// after
const res = queueLiveRunnerPrpCommand({ companyId, issueId, agentId, type: 'steer', payload });
try {
await res.completion;
} catch (e) {
if (e instanceof Error && e.message.startsWith('runner_prp_command_timeout:')) {
// runner did not finish in 30s: surface to user, optionally re-queue
throw new Error(`Steer command timed out; runner may be busy (run ${res.runId})`);
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check that a live authority exists before queueing
const hasLive = currentLiveAuthorities.has(liveAuthorityKey({ companyId, issueId, agentId }));
if (!hasLive) throw new Error('No live runner authority; command would be a no-op or rely on fallback'); Type guard
function isPrpCommandTimeout(e: unknown): e is Error & { commandId: string } {
const m = e instanceof Error && /^runner_prp_command_timeout:(.+)$/.exec(e.message);
if (m) (e as any).commandId = m[1];
return !!m;
} Try / catch
try {
await queueLiveRunnerPrpCommand({...}).completion;
} catch (e) {
if (isPrpCommandTimeout(e)) {
logger.warn({ commandId: e.commandId }, 'runner PRP command timed out; runner may be stalled');
// surface to caller / offer retry
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- --api-key and --api-key-env are mutually exclusive.
- runner_goal_stop_unconfirmed
- Failed to stop Daytona sandbox during lease release: ${forma
- provider turn ended with status ${turn.status}
- [opencode-local] Remote model availability probe for "${mode
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/eaca578f36ba7fda.
Report an issue: GitHub.