paperclipai/paperclip · error

native_adopted_runner_exited

native_adopted_runner_exited

Error message

native_adopted_runner_exited: runner exited before PRP authentication

What it means

While waiting for an adopted runner to authenticate, the wait loop polls whether the runner process is still alive. If the process has exited before PRP authentication completed, waiting longer is pointless and the durable session must be preserved for operator recovery, so the loop throws this coded error naming the exit-before-auth condition.

Source

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

    );
  let cancelled = false;
  let pollTimer: NodeJS.Timeout | undefined;
  let deadlineTimer: NodeJS.Timeout | undefined;
  const checkDeadline = () => {
    if (Date.now() >= deadline) throw timeoutError();
  };
  const observe = async () => {
    await input.ready?.();
    while (!cancelled) {
      input.throwIfFailed();
      checkDeadline();
      if (input.activeConnectionCount() === 1) return;
      const alive = await input.isAlive();
      if (cancelled) return;
      input.throwIfFailed();
      checkDeadline();
      if (!alive) {
        throw new Error(
          "native_adopted_runner_exited: runner exited before PRP authentication",
        );
      }
      if (input.activeConnectionCount() === 1) return;
      await new Promise<void>((resolveWait) => {
        pollTimer = setTimeout(
          resolveWait,
          Math.min(25, deadline - Date.now()),
        );
      });
    }
  };
  try {
    await Promise.race([
      observe(),
      input.failure,
      new Promise<never>((_resolve, reject) => {
        deadlineTimer = setTimeout(

View on GitHub (pinned to 01ad858492)

Solutions

  1. Check the runner process logs/stderr to determine why it exited before authentication
  2. Verify the runner binary exists, is executable, and matches the expected protocol version
  3. Restart/re-adopt the runner process, preserving its durable session state for recovery
  4. Increase runnerReconnectGraceMs only if the process is being killed by a timeout race, not if it is crashing

Example fix

// before
await awaitAdoptedRunnerAuthentication({ isAlive, ... }); // throws raw exit error
// after
try {
  await awaitAdoptedRunnerAuthentication({ isAlive, ... });
} catch (err) {
  if ((err as Error).message.startsWith("native_adopted_runner_exited")) {
    await recoverRunnerSession(); // inspect logs, restart runner, preserve durable session
  } else throw err;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!(await isAlive())) {
  throw new Error("runner already dead; restart it before awaiting PRP authentication");
}

Type guard

const runnerReadyForAuthWait = async (isAlive: () => Promise<boolean>) =>
  (await isAlive()) && Number.isSafeInteger(timeoutMs) && timeoutMs > 0;

Try / catch

try {
  await awaitAdoptedRunnerAuthentication({ ... });
} catch (err) {
  if ((err as Error).message.startsWith("native_adopted_runner_exited")) {
    await collectRunnerLogsAndRecover();
  } else throw err;
}

Prevention

When it happens

Trigger: Calling awaitAdoptedRunnerAuthentication (via #awaitAdoptedRunnerConnection) when isAlive() returns false during the polling loop — i.e., the adopted runner process died after adoption started but before it opened its single authenticated connection.

Common situations: The adopted CLI process crashed on startup (bad config, missing binary, version mismatch); the process was killed externally (OOM, supervisor); authentication hung long enough that the process was terminated; incompatible runner/protocol versions causing an immediate exit.

Understand the failure class

Related errors


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