paperclipai/paperclip · error

Unable to reserve OpenCode port

Error message

Unable to reserve OpenCode port

What it means

Thrown when reserving an ephemeral TCP port for the OpenCode server fails the invariant check: server.address() is null or returns a string (e.g. a Unix socket path / pipe name) instead of an AddressInfo with a numeric port. The port reservation is the first step of spawning the server, so startup aborts.

Source

Thrown at packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts:2393

      /* server is still starting */
    }
    await new Promise((resolve) => setTimeout(resolve, 50));
  }
  const detail = redact(diagnostics());
  throw new Error(
    `provider_initialize_timeout: provider=opencode stage=health${detail ? ` stderrTail=${detail}` : ""}`,
  );
}

async function reservePort(): Promise<number> {
  const server = createServer();
  await new Promise<void>((resolve, reject) => {
    server.once("error", reject);
    server.listen(0, "127.0.0.1", () => resolve());
  });
  const address = server.address();
  if (!address || typeof address === "string")
    throw new Error("Unable to reserve OpenCode port");
  const port = address.port;
  await new Promise<void>((resolve, reject) =>
    server.close((error) => (error ? reject(error) : resolve())),
  );
  return port;
}

async function* parseSseFrames(
  stream: ReadableStream<Uint8Array>,
): AsyncIterable<{ raw: string; data: string }> {
  const decoder = new TextDecoder();
  let buffer = "";
  for await (const chunk of stream as unknown as AsyncIterable<Uint8Array>) {
    buffer += decoder.decode(chunk, { stream: true });
    if (buffer.length > 1_048_576)
      throw new Error("OpenCode SSE event exceeded the retained payload limit");
    let boundary: RegExpExecArray | null;
    while ((boundary = /\r?\n\r?\n/.exec(buffer)) !== null) {

View on GitHub (pinned to 01ad858492)

Solutions

  1. Retry port reservation — a single failed attempt is usually transient.
  2. Verify the Node runtime's net module behaves normally (no patches, no restricted sandbox) and that 127.0.0.1 TCP binding is allowed.
  3. Check server 'error' events on the listener that might race with the resolve, leaving address() null.
  4. As a fallback, let the OS choose by listening on port 0 and re-reading address() after listen confirms, with explicit AddressInfo type narrowing.

Example fix

// before
const address = server.address();
if (!address || typeof address === "string")
  throw new Error("Unable to reserve OpenCode port");

// after
const address = server.address();
if (!address || typeof address === "string")
  throw new Error("Unable to reserve OpenCode port");
// caller: retry reserveOpenCodePort() up to N times before failing startup
Defensive patterns

Strategy: retry

Type guard

function isTcpAddress(a: unknown): a is import("node:net").AddressInfo {
  return typeof a === "object" && a !== null && typeof (a as any).port === "number";
}

Try / catch

try {
  port = await reserveOpenCodePort();
} catch (e) {
  if (e instanceof Error && e.message === "Unable to reserve OpenCode port") {
    port = await reserveOpenCodePort(); // single retry; persistent failure = environment issue
  } else throw e;
}

Prevention

When it happens

Trigger: net server listening on port 0 at 127.0.0.1 but address() returns null (server closed already) or a string (bound to a pipe/socket rather than TCP), which should not happen under normal conditions — indicates an environment or runtime anomaly.

Common situations: Hostile or misconfigured environment where IPv6-only/pipe binding changes address(); monkey-patched or restricted net module; extremely port-constrained environments causing listen errors surfaced differently.

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/f7f31c461df84c0b. Report an issue: GitHub.