paperclipai/paperclip · critical · Error
PAPERCLIP_BRIDGE_TOKEN is required.
Error message
PAPERCLIP_BRIDGE_TOKEN is required.
What it means
Thrown at top level of the zero-dependency bridge server that getSandboxCallbackBridgeServerSource() generates; the script runs INSIDE the sandbox and refuses to start unless PAPERCLIP_BRIDGE_TOKEN is set in its environment. The token is the shared secret the sandbox uses to authenticate callbacks to the host, so a missing token cannot be defaulted or ignored.
Source
Thrown at packages/adapter-utils/src/sandbox-callback-bridge.ts:1979
stream.on("response", (headers) => {
const rawStatus = headers[":status"];
status = typeof rawStatus === "number" ? rawStatus : Number(rawStatus) || 502;
responseHeaders = {};
for (const [key, value] of Object.entries(headers)) {
if (key.startsWith(":") || value == null) continue;
responseHeaders[key] = Array.isArray(value) ? value.join(", ") : String(value);
}
});
stream.on("data", (chunk: Buffer) => chunks.push(chunk));
stream.once("end", () => settle(() => resolve({ status, headers: responseHeaders, body: Buffer.concat(chunks) })));
stream.once("error", (error) =>
settle(() => reject(error instanceof Error ? error : new Error(String(error)))),
);
stream.once("aborted", () => settle(() => reject(new Error("Bridge HTTP/2 stream aborted."))));
if (request.body.length > 0) {
stream.end(request.body);
} else if (!stream.writableEnded) {
stream.end();
}
});
}
/**
* Create the sandbox HTTP/2 client gateway. It opens one HTTP/2 client
* session on the transport `createConnection` returns, and forwards each
* local request the caller hands it (already checked against the bridge
* token — see {@link SandboxHttp2BridgeGatewayRequest.receivedToken}) as one
* HTTP/2 stream. It keeps the header allowlist on the sandbox side, exactly
* as the file-mode gateway does.
*/
export function createSandboxHttp2BridgeGateway(
options: CreateSandboxHttp2BridgeGatewayOptions,
): SandboxHttp2BridgeGateway {
const authority = options.authority?.trim() || SANDBOX_HTTP2_GATEWAY_DEFAULT_AUTHORITY;
const headerAllowlist = options.headerAllowlist ?? DEFAULT_SANDBOX_CALLBACK_BRIDGE_HEADER_ALLOWLIST;
const session = http2.connect(`http://${authority}`, {View on GitHub (pinned to 01ad858492)
Solutions
- Ensure the sandbox process env includes PAPERCLIP_BRIDGE_TOKEN with the token the host minted for this sandbox (pass it in the spawn options' env, merged with process.env).
- If you run the generated script manually, export a token first: PAPERCLIP_BRIDGE_TOKEN=$(paperclip ... ) node bridge.mjs.
- Check for an env-scrubbing wrapper (docker --env-file omission, su -l, set -u with unset vars) between the host launcher and the bridge process.
- Never log the token while debugging; verify presence with [ -n "$PAPERCLIP_BRIDGE_TOKEN" ] instead of printing it.
Example fix
// before
const child = spawn(process.execPath, [bridgeScriptPath], {
env: { PAPERCLIP_BRIDGE_QUEUE_DIR: queueDir },
});
// after
const child = spawn(process.execPath, [bridgeScriptPath], {
env: { ...process.env, PAPERCLIP_BRIDGE_QUEUE_DIR: queueDir, PAPERCLIP_BRIDGE_TOKEN: mintedToken },
}); Defensive patterns
Strategy: validation
Validate before calling
// Host side, before spawning the bridge inside the sandbox:
if (!bridgeToken) {
throw new Error("refusing to start sandbox callback bridge: no token minted");
}
const env = { ...sandboxEnv, PAPERCLIP_BRIDGE_TOKEN: bridgeToken }; Try / catch
try {
await startBridge(sandbox, token);
} catch (error) {
if (error instanceof Error && error.message.includes("PAPERCLIP_BRIDGE_TOKEN")) {
// env injection bug on the host side; re-mint and re-spawn, never start the bridge without it
throw new Error("bridge env missing token; check sandbox spawn env injection");
}
throw error;
} Prevention
- Always merge the token into the child env explicitly at the spawn site; do not rely on inherited env.
- Watch for env-scrubbing wrappers (docker env-file, su -l, sanitizers) between host and bridge.
- Verify presence with a boolean check, never by printing the token.
- In duplex mode remember QUEUE_DIR is not required; in file mode it is — check both up front.
When it happens
Trigger: Spawning the generated bridge source without PAPERCLIP_BRIDGE_TOKEN in env; a host-side launcher that fails to inject the minted token into the sandbox process env; manually copy-pasting the generated script into a shell for debugging without exporting the variable.
Common situations: Refactoring the sandbox spawn path and dropping the env entry; environment scrubbing (a wrapper that clears env before exec); debugging the bridge standalone outside Paperclip; note file mode additionally requires PAPERCLIP_BRIDGE_QUEUE_DIR (separate error) while duplex mode only needs the token.
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
Related errors
- The bridge host reached its reserved process body byte ceili
- Failed to stop Daytona sandbox during lease release: ${forma
- [adapter-ui-loader] Failed to load UI parser for "${adapterT
- ${prefix}: "sandboxTransport" must be one of ${ADAPTER_LOGIN
- ${options.errorLabel} decision predicate failed: ${detail}
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-08-21).
Data as JSON: /api/errors/30deb577576ca661.
Report an issue: GitHub.