paperclipai/paperclip · warning
runner_prp_command_timeout
runner_prp_command_timeout
Error message
runner_prp_command_timeout:${commandId} What it means
waitForCommand polls the authority for the command outcome every 10ms until deadline (default 30s). If the command still has no terminal outcome when the deadline passes, it throws 'runner_prp_command_timeout:<id>'. This means the command exists but the runner never completed, failed, or rejected it within the allotted window.
Source
Thrown at server/src/services/native-runtime/runner-prp-coordinator.ts:416
waitForCommand: async (commandId, timeoutMs = 30_000) => {
if (released) throw new Error("runner_prp_session_released");
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const outcome = authority.commandOutcome(commandId);
if (!outcome) throw new Error(`runner_prp_command_missing:${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}:${commandId}`;
throw new Error(message);
}
await new Promise<void>((resolve) => {
const timer = setTimeout(resolve, 10);
timer.unref();
});
}
throw new Error(`runner_prp_command_timeout:${commandId}`);
},
waitForGoalEvent: async (requestId, timeoutMs = 30_000) => {
if (released) throw new Error("runner_prp_session_released");
if (observedGoalRequests.has(requestId)) {
const error = observedGoalRequests.get(requestId);
if (error) throw new Error(error);
return;
}
let timer: NodeJS.Timeout | null = null;
let resolveGoalEvent!: () => void;
let rejectGoalEvent!: (error: Error) => void;
const goalEvent = new Promise<void>((resolve, reject) => {
resolveGoalEvent = resolve;
rejectGoalEvent = reject;
});
const waiter = { resolve: resolveGoalEvent, reject: rejectGoalEvent };
const waiters = goalEventWaiters.get(requestId) ?? new Set<typeof waiter>();
waiters.add(waiter);View on GitHub (pinned to 01ad858492)
Solutions
- Increase the timeoutMs argument to match the expected command duration (e.g. 300_000 for human-in-the-loop questions).
- Catch /runner_prp_command_timeout:(.+)/ and re-poll via a fresh waitForCommand or check the outcome out-of-band instead of failing the run immediately.
- Verify the runner connection is healthy; after a disconnect the outcome will never arrive, so re-queue the command on a new session rather than waiting.
- Check runner-side processing logs for the commandId to find why it never reached a terminal state; fix the wedged handler.
Example fix
// before const result = await session.waitForCommand(commandId); // default 30s // after const result = await session.waitForCommand(commandId, 5 * 60_000); // allow 5 min
Defensive patterns
Strategy: retry
Validate before calling
// size the timeout to the command type before waiting:
const TIMEOUT_BY_TYPE = { question: 5 * 60_000, tool_call: 30_000 };
await session.waitForCommand(commandId, TIMEOUT_BY_TYPE[type] ?? 30_000); Type guard
const isCommandTimeout = (e: unknown): e is Error => e instanceof Error && /^runner_prp_command_timeout:/.test(e.message);
Try / catch
async function waitWithRetry(session, commandId, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try {
return await session.waitForCommand(commandId, 30_000);
} catch (e) {
if (isCommandTimeout(e) && i < attempts - 1) continue;
throw e;
}
}
} Prevention
- Choose timeoutMs per command type; the 30s default is too short for human-in-the-loop commands.
- Monitor runner connectivity so you can distinguish slow commands from dead connections.
- Add progress events/checkpoints on the runner for long operations so outcomes arrive before the deadline.
- Alert on repeated command timeouts — they usually indicate a wedged runner worker, not transient latency.
When it happens
Trigger: Runner is connected but slow or wedged (long-running command exceeding the 30s default); the runner disconnected mid-command so the outcome never arrives; the caller passes an explicit timeoutMs that is too short; command queue backlog delays processing past the deadline.
Common situations: Waiting on an interactive user-question command with the default 30s while the human takes longer; network stall between controller and runner; an overloaded runner worker not draining the command queue; passing timeoutMs=30000 explicitly for operations known to take minutes.
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
- runner_prp_command_missing
- ${outcome.result.message} (fallback: runner_prp_command_${ou
- 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/946329e1e95b89af.
Report an issue: GitHub.