paperclipai/paperclip · critical

Sandbox callback bridge did not report a listening port.

Error message

Sandbox callback bridge did not report a listening port.

What it means

Thrown when the parsed readiness JSON exists but its `port` field is missing, zero, or non-finite. The bridge server is expected to bind a TCP port and report it; a port of 0/NaN means the server either failed to listen (e.g. port 0 ephemeral was requested but the write of the real port never happened) or wrote a non-numeric port. This is a fail-closed guard so the orchestrator never returns a baseUrl pointing at port 0.

Source

Thrown at packages/adapter-utils/src/sandbox-callback-bridge.ts:1113

    shellCommand,
  );
  requireSuccessfulResult("wait for sandbox callback bridge readiness", readyResult);

  let readyData: { host?: string; port?: number; baseUrl?: string; pid?: number };
  try {
    readyData = JSON.parse(readyResult.stdout.trim()) as { host?: string; port?: number; baseUrl?: string; pid?: number };
  } catch (error) {
    throw new Error(
      `Sandbox callback bridge wrote invalid readiness JSON: ${error instanceof Error ? error.message : String(error)}`,
    );
  }

  const host = typeof readyData.host === "string" && readyData.host.trim().length > 0
    ? readyData.host.trim()
    : "127.0.0.1";
  const port = typeof readyData.port === "number" && Number.isFinite(readyData.port) ? readyData.port : 0;
  if (!port) {
    throw new Error("Sandbox callback bridge did not report a listening port.");
  }
  const baseUrl =
    typeof readyData.baseUrl === "string" && readyData.baseUrl.trim().length > 0
      ? readyData.baseUrl.trim()
      : `http://${host}:${port}`;

  return {
    baseUrl,
    host,
    port,
    pid: typeof readyData.pid === "number" && Number.isFinite(readyData.pid) ? readyData.pid : 0,
    directories,
    stop: async () => {
      const stopResult = await input.runner.execute({
        command: shellCommand,
        args: shellCommandArgs(
          [
            `if [ -s ${shellQuote(directories.pidFile)} ]; then`,

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Read the generated bridge server source (getSandboxCallbackBridgeServerSource) and confirm it writes the real bound port into ready.json after `server.listen`.
  2. Check directories.logFile on the remote host for a bridge runtime error after the listen call.
  3. Confirm PAPERCLIP_BRIDGE_PORT was not forced to a non-listenable value and that the bridge host can bind.
  4. Log readyData verbatim before the port check to see which field is malformed.
Defensive patterns

Strategy: validation

Validate before calling

const readyData = JSON.parse(raw) as { port?: unknown };
if (typeof readyData.port !== 'number' || !Number.isFinite(readyData.port) || readyData.port <= 0) {
  throw new Error(`Bridge did not bind a port; readyData=${JSON.stringify(readyData)}`);
}

Type guard

function hasValidPort(v: unknown): v is { port: number } {
  return typeof (v as { port?: unknown })?.port === 'number'
    && Number.isFinite((v as { port: number }).port)
    && (v as { port: number }).port > 0;
}

Prevention

When it happens

Trigger: startSandboxCallbackBridge parsed readyData successfully but readyData.port is undefined, 0, Infinity, -1, or a string coerced away. Occurs when the remote bridge binds but its ready.json write omits `port`, or writes port 0 because process.env.PAPERCLIP_BRIDGE_PORT defaulted and the actual bound port was not written back.

Common situations: Bridge server code path changed and stopped emitting port; the `server.listen(0)` callback that writes ready.json was replaced; a port string vs number type regression in the bridge source generator; bridge process started but crashed after listening, before writing port.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/f6863f2695d08c17. Report an issue: GitHub.