paperclipai/paperclip · error

native_runner_prp_run_rotation_failed

Error message

native_runner_prp_run_rotation_failed

What it means

attachRun sends a 'run.attach' command to the runner core and waits for it to complete. If the command finishes with any status other than 'completed', the run rotation failed and this error is thrown. It signals the runner rejected or failed the durable identity rotation for the run.

Source

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

            this.#authorizedTools,
            this.options.resumeCompletionContract,
          )
        : rotatedRunAttachPayload(
            core.store.state,
            desired,
            this.#authorizedTools,
            this.options.resumeCompletionContract,
          );
      this.#runAttachTemplate = structuredClone(runAttachTemplate);
      const payload = {
        ...runAttachTemplate,
        paperclipNextAuthority: { identity: desired, connection },
      };
      core.queueCommand("run.attach", payload, commandId, true);
      await this.#waitCommand("run.attach", commandId);
      const attached = core.getCommand(commandId);
      if (attached?.status !== "completed") {
        throw new Error("native_runner_prp_run_rotation_failed");
      }

      activationStarted = true;
      core.rotateRunIdentity(desired, runAttachTemplate);
      await registration?.activate?.();
      if (registration?.failure) {
        void registration.failure.catch((error: unknown) => {
          this.#failTransport(
            error instanceof Error ? error : new Error(String(error)),
          );
        });
      }
      await this.#awaitRegistrationReady(registration?.ready);
      const activationDeadline =
        Date.now() + (this.options.runnerReconnectGraceMs ?? 5_000);
      while (
        !recoveryIdentityMatches(core.store.state.identity, desired) ||
        core.store.state.warmTransition !== undefined ||

View on GitHub (pinned to 01ad858492)

Solutions

  1. Inspect runner logs for why the run.attach command failed
  2. Verify runId/turnId/itemId are valid and match an existing run on the runner
  3. Ensure transport and runner versions agree on the run.attach protocol
  4. Retry the attach after fixing the runner-side cause

Example fix

// before
await transport.attachRun({ runId: staleRunId, turnId, itemId });
// after
const run = await resolveRun(staleRunId);
if (!run) throw new Error(`run ${staleRunId} not found on runner`);
await transport.attachRun({ runId: run.id, turnId, itemId });
Defensive patterns

Strategy: try-catch

Validate before calling

if (!runId || !turnId || !itemId) throw new Error('attachRun requires runId, turnId, itemId');

Try / catch

try {
  await transport.attachRun({ runId, turnId, itemId });
} catch (e) {
  if (e.message === 'native_runner_prp_run_rotation_failed') {
    const detail = await fetchRunnerAttachFailure(runId);
    logger.error('run.attach rejected by runner', detail);
    throw new Error(`run rotation failed: ${detail}`);
  } else throw e;
}

Prevention

When it happens

Trigger: core.getCommand(commandId).status is 'failed' or otherwise non-completed after #waitCommand resolves for the 'run.attach' payload — e.g. the runner rejected the desired DurableRecoveryIdentity or the attach handler errored.

Common situations: Runner-side attach failure due to invalid runId/turnId/itemId; runner state incompatible with the desired identity; protocol mismatch between transport and runner versions.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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