paperclipai/paperclip · error · Error

No available loopback port found at or above ${preferredPort

Error message

No available loopback port found at or above ${preferredPort}.

What it means

Like the other PRP rotation guards, this fires when the run rotation must prepare and rotate an EXTERNAL authority epoch but the required external state callbacks are absent. Here three callbacks are required: `readRunnerState`, `prepareExternalRunnerState`, and `archiveExternalRunnerState`; any missing one causes the throw after `prepareExternalRunnerState` could not even be invoked.

Source

Thrown at cli/src/commands/test-drive.ts:157

  return fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-test-drive-"));
}

async function loopbackPortAvailable(port: number): Promise<boolean> {
  return await new Promise<boolean>((resolve) => {
    const server = createServer();
    server.unref();
    server.once("error", () => resolve(false));
    server.listen(port, "127.0.0.1", () => {
      server.close(() => resolve(true));
    });
  });
}

export async function resolveTestDriveServerPort(preferredPort = 3100): Promise<number> {
  for (let port = preferredPort; port <= 65_535; port += 1) {
    if (await loopbackPortAvailable(port)) return port;
  }
  throw new Error(`No available loopback port found at or above ${preferredPort}.`);
}

/**
 * Establish isolation before the CLI's normal config and .env loading hook.
 * The selected credential source is preserved in case its name happens to use
 * a PAPERCLIP_ prefix; all other Paperclip routing/configuration is discarded.
 */
export async function prepareTestDriveEnvironment(
  options: Pick<TestDriveOptions, "dataDir" | "apiKeyEnv">,
  cwd = process.cwd(),
): Promise<{ dataDir: string; linkedWorktree: boolean }> {
  const sourceEnvName = options.apiKeyEnv?.trim();
  const preservedCredential = sourceEnvName ? process.env[sourceEnvName] : undefined;

  for (const key of Object.keys(process.env)) {
    if (key.startsWith("PAPERCLIP_")) {
      delete process.env[key];
    }

View on GitHub (pinned to 01ad858492)

Solutions

  1. Supply all three callbacks (`readRunnerState`, `prepareExternalRunnerState`, `archiveExternalRunnerState`) in the transport options.
  2. Route the rotation through warm recovery when a recovery proof is available so external cold-start callbacks are not needed.
  3. Audit the options construction site so no callback is dropped by conditional spread or defaults.

Example fix

// before
new RunnerdCodexTransport({ readRunnerState, archiveExternalRunnerState });
// after
new RunnerdCodexTransport({
  readRunnerState,
  prepareExternalRunnerState: async () => prepareState(),
  archiveExternalRunnerState,
});
Defensive patterns

Strategy: validation

Validate before calling

const required = ["readRunnerState", "prepareExternalRunnerState", "archiveExternalRunnerState"] as const;
for (const k of required) {
  if (typeof options[k] !== "function") throw new Error(`${k} callback required`);
}

Type guard

function hasAllExternalCallbacks(o: unknown): boolean {
  const x = o as Record<string, unknown>;
  return ["readRunnerState","prepareExternalRunnerState","archiveExternalRunnerState"]
    .every((k) => typeof x[k] === "function");
}

Try / catch

try {
  await transport.start(params);
} catch (e) {
  if (e.message === "native_runner_prp_run_rotation_unavailable") {
    transport.reconfigure({ ...options, prepareExternalRunnerState, readRunnerState, archiveExternalRunnerState });
  }
}

Prevention

When it happens

Trigger: A cold (non-exact-authority, non-warm-recovery) run rotation targeting an externally-owned state store where any of `readRunnerState`, `prepareExternalRunnerState`, or `archiveExternalRunnerState` is `undefined` in the transport options.

Common situations: Partial wiring of external-state plumbing when migrating between state owners; options objects built conditionally in tests or scripts; upgrading the transport and not noticing new required callbacks.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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