paperclipai/paperclip · error · Error

runner_prp_command_${outcome.status}:${command.commandId}

Error message

runner_prp_command_${outcome.status}:${command.commandId}

What it means

When the authority reports a queued PRP command as failed or rejected, queueLiveRunnerPrpCommand throws with the outcome's message if it is a string, otherwise this synthesized 'runner_prp_command_<status>:<commandId>' message. It surfaces the remote command outcome to the steering caller.

Source

Thrown at server/src/realtime/runner-prp-ws.ts:216

  );
  return {
    runId,
    commandId: command.commandId,
    controllerSeq: command.controllerSeq,
    completion: (async () => {
      const deadline = Date.now() + 30_000;
      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);

View on GitHub (pinned to 01ad858492)

Solutions

  1. Read the status embedded in the message (failed vs rejected) to decide retry vs abort
  2. Inspect runner logs for the commandId to find the underlying failure
  3. Resend the command if the failure was transient; do not resend if rejected due to run state
  4. Have the runner return a string result.message so callers get actionable errors

Example fix

// before
await steer(runId, text); // throws runner_prp_command_failed:<id>
// after
try {
  await steer(runId, text);
} catch (e) {
  if (String(e.message).startsWith('runner_prp_command_rejected')) {
    // run state rejected it; do not blind-retry
  } else if (String(e.message).startsWith('runner_prp_command_failed')) {
    await steer(runId, text); // transient; retry
  } else throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check run turn state before steering
const state = await getRunState(runId); if (state.busy) await waitForIdle(runId);

Type guard

const hasMessage = (r) => !!r && typeof r.message === 'string';

Try / catch

try { await steer(runId, text) } catch (e) { if (/runner_prp_command_rejected/.test(e.message)) abort(); else if (/runner_prp_command_failed/.test(e.message)) retryOnce(); else throw e; }

Prevention

When it happens

Trigger: steer/nativeControl command is delivered but the runner authority completes it with status failed or rejected and no string message in outcome.result.

Common situations: Runner rejected a steer because the turn was busy or in an invalid state; native control command failed on the runner; runner returned a non-string error payload.

Related errors


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