paperclipai/paperclip · warning
[paperclip] sandbox callback bridge failed to abort queued…
Error message
[paperclip] sandbox callback bridge failed to abort queued requests after worker failure: ${failPendingError instanceof Error ? failPendingError.message : String(failPendingError)} What it means
The bridge worker loop caught a fatal error (e.g. per-iteration timeout while listing or processing request files), surfaced it as a run-trace span via surfaceRunError, then called failPendingRequests(message, { abandonInFlight: true }) to abort queued requests with 503s. That abort itself threw — so this warning reports the cleanup failure. Queued request files may remain, and their sandbox callers stay pending until their own deadlines; the run has already recorded the primary worker failure.
Solutions
- Find the primary failure first: the run trace now contains the surfaced worker-failure span (surfaceRunError) — read it to see the root timeout/error.
- Verify and restore sandbox/storage health, then stop and re-run the affected agent turn so a fresh bridge drains or 503s leftover requests.
- Do not retry the run against the same sandbox until the channel responds (a quick list/write probe against the shared dir is a good smoke test).
- Report with run logs if recovery keeps failing on a healthy channel — that combination indicates a defect.
Defensive patterns
Strategy: try-catch
Validate before calling
const probe = await withTimeout(input.client.listJsonFiles(directories.requestsDir), iterationTimeoutMs, "probe"); // channel answers => safe to attempt failPendingRequests; otherwise expect stranded queue
Try / catch
try {
await failPendingRequests(message, { abandonInFlight: true });
} catch (failPendingError) {
// Best-effort cleanup failed on the same dead channel. The primary failure was
// already surfaced to the run trace — log and let callers hit their own deadlines.
console.warn(`[paperclip] sandbox callback bridge failed to abort queued requests: ${String(failPendingError)}`);
} Prevention
- Surface the primary worker error to the run trace (surfaceRunError) so cleanup failure never hides the root cause.
- Stop and re-run the agent turn after restoring sandbox health so a fresh bridge drains leftover requests.
- Proactively probe the channel before recovery sweeps when a timeout just occurred.
- Alert on this warn in run telemetry — it predicts stranded sandbox callers.
When it happens
Trigger: Sandbox channel dies while requests are queued: the loop's listJsonFiles or a request write times out (worker failure), and the subsequent recovery sweep that writes 503 responses fails again on the same dead channel.
Common situations: Sandbox killed mid-run; shared storage unmounted; severe host I/O contention making every channel operation time out in sequence.
Related errors
- [paperclip] sandbox callback bridge kept queued request
- CreateOS command cleanup failed; process termination is…
- CreateOS lease cleanup is already in progress.
- CreateOS lease cleanup must finish before resume.
- CreateOS process cleanup is unconfirmed; destroy this lease…
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-08-18).
Data as JSON: /api/errors/0652120ef5ad5e8d.
Report an issue: GitHub.
Appendix: source
Thrown at packages/adapter-utils/src/sandbox-callback-bridge.ts:1535
consecutivePollFailures += 1;
const message = `${buildWorkerFailureMessage(error)} (transient poll failure ${consecutivePollFailures}; retrying)`;
if (consecutivePollFailures === 1) {
// Put the first failure of a streak on the run trace; later repeats
// only warn, so a flapping channel does not spam failed spans.
await surfaceRunError(new Error(message));
} else {
console.warn(`[paperclip] ${message}`);
}
const backoffMs = Math.min(
pollIntervalMs * 2 ** consecutivePollFailures,
MAX_TRANSIENT_ITERATION_BACKOFF_MS,
);
await new Promise((resolve) => setTimeout(resolve, backoffMs));
continue;
}
// A file whose attempt is still in flight (or waiting on its 504
// backstop) is not actionable: `processRequestFile` would skip it via
// the guard map. Treat an all-guarded listing like an empty one and
// sleep a poll interval. Re-listing immediately would spin the loop —
// an exec storm against a real sandbox channel, and with an in-memory
// client a pure-microtask loop that starves every timer in the process
// (including the guard's own backstop and abort timers).
const actionableFileNames = fileNames.filter((fileName) => !inFlightRequestGuards.has(fileName));
if (actionableFileNames.length === 0) {
lastSuccessfulIterationAt = Date.now();
if (stopping) {
break;
}
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
continue;
}
for (const fileName of actionableFileNames) {
if (stopping && Date.now() >= stopDeadline) break;
inFlight += 1;
try {
// A request is run-time work, not startup work. Wrap it in aView on GitHub (pinned to 3f1d897a7c)