paperclipai/paperclip · critical

Sandbox callback bridge wrote invalid readiness JSON: ${erro

Error message

Sandbox callback bridge wrote invalid readiness JSON: ${error instanceof Error ? error.message : String(error)}

What it means

Thrown after the sandbox callback bridge server starts and the readiness wait loop returns, when the contents of the ready file cannot be parsed as JSON. The orchestrator expects the remote node bridge process to write a {host, port, baseUrl, pid} object to a ready.json file; this error means that file's stdout/stdout payload was empty, truncated, or non-JSON (e.g. it contains a shell error or a node stack trace). It surfaces a corrupt or aborted bridge startup rather than a caller-input problem.

Source

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

      "    exit 1",
      "  fi",
      "  i=$((i + 1))",
      "  sleep 0.05",
      "done",
      `echo "Timed out waiting for bridge readiness." >&2`,
      `if [ -s ${shellQuote(directories.logFile)} ]; then cat ${shellQuote(directories.logFile)} >&2; fi`,
      "exit 1",
    ].join("\n"),
    timeoutMs,
    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,

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Inspect the captured readiness payload: log readyResult.stdout.trim() right before JSON.parse to see the exact non-JSON bytes.
  2. Check the remote bridge log file (directories.logFile) and stderr — the node bridge startup error is usually printed there.
  3. Verify the remote node binary works (nodeCommand resolves on the sandbox host and matches a supported version) and that PAPERCLIP_BRIDGE_QUEUE_DIR/PAPERCLIP_BRIDGE_TOKEN are set for the bridge process.
  4. Ensure no shell login banner or wrapper prints to stdout between the wait loop's `cat ready.json` and exit.

Example fix

// before
readyData = JSON.parse(readyResult.stdout.trim()) as {...};
// after — diagnose the payload before failing
const raw = readyResult.stdout.trim();
try {
  readyData = JSON.parse(raw) as {...};
} catch (error) {
  throw new Error(
    `Sandbox callback bridge wrote invalid readiness JSON: ${error instanceof Error ? error.message : String(error)}\nPayload was: ${raw}`,
  );
}
Defensive patterns

Strategy: validation

Validate before calling

// Before starting the bridge, validate the remote node can emit valid JSON.
// After start, inspect stdout before parsing:
const raw = readyResult.stdout.trim();
if (!raw.startsWith('{') || !raw.endsWith('}')) {
  throw new Error(`Bridge readiness payload is not a JSON object: ${raw.slice(0, 200)}`);
}
const readyData = JSON.parse(raw);

Type guard

function isBridgeReadyData(v: unknown): v is { host?: string; port?: number; baseUrl?: string; pid?: number } {
  return typeof v === 'object' && v !== null;
}

Try / catch

try {
  readyData = JSON.parse(readyResult.stdout.trim()) as {...};
} catch (error) {
  // Include raw payload in the surfaced error for fast diagnosis.
  throw new Error(`Bridge readiness JSON invalid: ${(error as Error).message}; payload=${readyResult.stdout.slice(0, 500)}`);
}

Prevention

When it happens

Trigger: Calling startSandboxCallbackBridge (or the prepare path that uses it): the readiness wait command exits 0 (requireSuccessfulResult passed) but readyResult.stdout is not valid JSON. Happens when stdout carries a partial line, a leading log message, or the bridge wrote ready.json with a syntax error. Also triggered if a concurrent process writes to the same stdout that the wait loop cats.

Common situations: Bridge node process emits a warning to stdout before writing ready JSON; a mismatched node version on the remote host throws before the JSON write; the ready.json was hand-edited or partially flushed; a remote shell prints a login banner that pollutes stdout captured by the wait loop.

Related errors


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