paperclipai/paperclip · error · Error

runner_prp_command_missing:${command.commandId}

Error message

runner_prp_command_missing:${command.commandId}

What it means

Thrown by queueLiveRunnerPrpCommand when, after the command is queued on the live binding, the authority's commandOutcome lookup returns nothing for the commandId within the polling loop — i.e. the command was never registered or was dropped before producing an outcome. The message carries the missing commandId.

Source

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

  const binding = registrations.get(current.runId);
  if (!binding || binding.generation !== current.generation) return null;
  const runId = current.runId;
  const command = binding.authority.queueCommand(
    input.type,
    input.payload ?? {},
    input.commandId,
    true,
  );
  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}`);
    })(),
  };
}

View on GitHub (pinned to 01ad858492)

Solutions

  1. Verify the command was accepted/registered by the authority before polling its outcome
  2. Re-acquire the current authority binding and resend the command with a new commandId
  3. Handle runner reconnects: invalidate in-flight commands on re-registration
  4. Check for race between queueLiveRunnerPrpCommand and authority re-registration

Example fix

// before
await queueLiveRunnerPrpCommand(binding, command); // throws runner_prp_command_missing
// after
try {
  await queueLiveRunnerPrpCommand(binding, command);
} catch (e) {
  if (String(e.message).startsWith('runner_prp_command_missing')) {
    const fresh = getLiveBinding(command.runId);
    return queueLiveRunnerPrpCommand(fresh, { ...command, commandId: newId() });
  }
  throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

if (!getLiveBinding(command.runId) || binding.generation !== currentGeneration) await reacquireBinding();

Try / catch

try { await queueLiveRunnerPrpCommand(binding, cmd) } catch (e) { if (String(e.message).startsWith('runner_prp_command_missing')) { return resendOnFreshBinding(cmd); } throw e; }

Prevention

When it happens

Trigger: Calling steer/nativeControl on a live runner binding whose authority lost or never recorded the command (e.g. authority re-registered between queueing and outcome lookup, command id mismatch).

Common situations: Runner reconnected mid-command so the old authority's outcome map is gone; bug dropping the command before registration; stale binding used after reconnect.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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