paperclipai/paperclip · error

PRP command ${type} ${command.status}: ${JSON.stringify(comm

Error message

PRP command ${type} ${command.status}: ${JSON.stringify(command.result)}

What it means

Thrown from `#waitCommand` when the awaited PRP command record exists but its status is neither `pending` nor `completed` (e.g. `failed`, `cancelled`, `timeout`). The message embeds the status and the JSON of `command.result`, so the durable failure payload is visible in the error.

Source

Thrown at packages/paperclip-runner/src/live/runnerd-codex-transport.ts:5550

    throw new Error("runnerd did not report its provider identity");
  }

  async #waitCommand(
    type: string,
    commandId?: string,
    deadline = Date.now() + 30_000,
  ): Promise<void> {
    while (Date.now() < deadline) {
      this.#throwIfFailed();
      const command =
        commandId === undefined
          ? this.#core?.store.state.commands.find(
              (candidate) => candidate.type === type,
            )
          : this.#core?.getCommand(commandId);
      if (command?.status === "completed") return;
      if (command !== undefined && command.status !== "pending") {
        throw new Error(
          `PRP command ${type} ${command.status}: ${JSON.stringify(command.result)}`,
        );
      }
      if (await this.#runnerHasExited())
        throw new Error(`runnerd exited while waiting for ${type}`);
      await new Promise((resolveWait) => setTimeout(resolveWait, 10));
    }
    throw new Error(
      `${this.#startupComplete ? "provider_transport_failed" : this.#startupFailureCode}: PRP command ${type} timed out`,
    );
  }


  #pumpEvents(): void {
    const core = this.#core;
    // The controller may activate its new epoch before attachRun observes the
    // confirmed handoff. Never consume either epoch with the other's cursor.
    if (

View on GitHub (pinned to 01ad858492)

Solutions

  1. Read the `command.result` JSON embedded in the message — it contains runnerd's reason for the failure; fix the underlying cause and retry.
  2. Retry the command with a fresh commandId.
  3. When a commandId is not supplied, always pass one so the lookup does not match an older failed command of the same type.
  4. Check runnerd logs for the command ID to see the full failure context; restart runnerd if the failure is internal.

Example fix

// before
const command = this.#core?.store.state.commands.find((c) => c.type === type);
// after
const command = commandId
  ? this.#core?.getCommand(commandId)
  : this.#core?.store.state.commands.find((c) => c.type === type && c.status !== "failed");
Defensive patterns

Strategy: try-catch

Validate before calling

const cmd = core.getCommand(commandId);
if (cmd && cmd.status !== "pending" && cmd.status !== "completed") {
  throw new Error(`command ${commandId} already terminal: ${cmd.status}`);
}

Type guard

function isRetryableCommandFailure(err) {
  return err instanceof Error && /^PRP command .* (failed|cancelled|timeout):/.test(err.message);
}

Try / catch

try {
  await waitCommand(type, commandId);
} catch (err) {
  if (isRetryableCommandFailure(err)) {
    log.error("PRP command failed", { result: err.message });
    await requeueCommand(type, payload);
  } else throw err;
}

Prevention

When it happens

Trigger: Waiting on a command (by explicit `commandId`, or by scanning `store.state.commands` for the first command of the given type when no id is supplied) and observing a terminal non-success status during the 10ms poll loop.

Common situations: runnerd rejected the command payload (validation failure recorded in `result`); the codex subprocess failed mid-command; command deadline exceeded inside runnerd; scanning by type when no id is passed picks up an unrelated failed command of the same type.

Related errors


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