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

  1. Increase the timeoutMs argument to match the expected command duration (e.g. 300_000 for human-in-the-loop questions).
  2. 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.
  3. 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.
  4. 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

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.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/946329e1e95b89af. Report an issue: GitHub.