paperclipai/paperclip · error

stop sandbox callback bridge timed out${detail ? `: ${detail

Error message

stop sandbox callback bridge timed out${detail ? `: ${detail}` : ""}

What it means

Thrown by the `stop` callback of a started sandbox callback bridge when the kill-and-wait command ran longer than timeoutMs. The stop routine sends SIGTERM to the recorded pid, polls up to 40 times (≈2s) for the process to exit, then removes pid/ready files; if the whole execute() call reports timedOut, this error fires. It signals the remote bridge process did not die within the configured shutdown budget.

Source

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

            `  pid="$(cat ${shellQuote(directories.pidFile)})"`,
            "  kill \"$pid\" 2>/dev/null || true",
            "  i=0",
            "  while kill -0 \"$pid\" 2>/dev/null && [ \"$i\" -lt 40 ]; do",
            "    i=$((i + 1))",
            "    sleep 0.05",
            "  done",
            "fi",
            `rm -f ${shellQuote(directories.pidFile)} ${shellQuote(directories.readyFile)}`,
          ].join("\n"),
        ),
        cwd: input.remoteCwd,
        env: {
          [SANDBOX_EXEC_CHANNEL_ENV]: SANDBOX_EXEC_CHANNEL_BRIDGE,
        },
        timeoutMs,
      });
      if (stopResult.timedOut) {
        throw new Error(buildRunnerFailureMessage("stop sandbox callback bridge", stopResult));
      }
    },
  };
}

function getSandboxCallbackBridgeServerSource(): string {
  return `import { randomUUID, timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";
import { promises as fs } from "node:fs";
import path from "node:path";

const queueDir = process.env.PAPERCLIP_BRIDGE_QUEUE_DIR;
const bridgeToken = process.env.PAPERCLIP_BRIDGE_TOKEN;
const host = process.env.PAPERCLIP_BRIDGE_HOST || "127.0.0.1";
const port = Number(process.env.PAPERCLIP_BRIDGE_PORT || "0");
const pollIntervalMs = Number(process.env.PAPERCLIP_BRIDGE_POLL_INTERVAL_MS || "100");
const responseTimeoutMs = Number(process.env.PAPERCLIP_BRIDGE_RESPONSE_TIMEOUT_MS || "30000");
const maxQueueDepth = Number(process.env.PAPERCLIP_BRIDGE_MAX_QUEUE_DEPTH || "${DEFAULT_BRIDGE_MAX_QUEUE_DEPTH}");

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Increase the timeoutMs passed to startSandboxCallbackBridge so teardown has more headroom.
  2. Check whether the bridge node process is genuinely gone on the remote host after the error (ps for the pid from directories.pidFile).
  3. Ensure the bridge server closes its HTTP server and pending connections on SIGTERM (review getSandboxCallbackBridgeServerSource signal handling).
  4. Clean stale pidFile/readyFile from directories before a fresh start to avoid pid reuse confusion.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await bridge.stop();
} catch (err) {
  // Log and continue; the run is ending. Optionally attempt a forceful SIGKILL.
  logger.warn('bridge stop failed/timed out', { error: (err as Error).message });
}

Prevention

When it happens

Trigger: Calling the returned `stop()` function (bridge teardown at run end) where the remote shell execute timed out. Happens when the bridge node process ignores SIGTERM, the pidFile points at a zombie/reused pid, or the remote exec channel itself is stalled so even `kill` does not return.

Common situations: Bridge server has an open long-lived connection keeping the event loop alive; pid was recycled and now belongs to an unrelated process; remote host is under heavy load so the 40-iteration wait never sees exit; an earlier start left a stale pidFile pointing at a live process.

Understand the failure class

Related errors


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