paperclipai/paperclip · warning
The stopped Daytona sandbox is still settling cancelled…
Error message
The stopped Daytona sandbox is still settling cancelled work. Retry shortly.
What it means
The Daytona sandbox provider throws this when an admission attempt (e.g. re-acquiring/restarting a lease) targets a sandbox that has been confirmed stopped but still has in-flight SDK activity from a previous request settling (cancelled work still draining). Restarting the sandbox under that old request could let it write or execute again, so the provider refuses and asks the caller to retry shortly.
Solutions
- Wait briefly and retry the operation — the gate clears once the cancelled work finishes settling.
- Add retry with backoff around lease admission when you recently stopped the sandbox.
- Ensure your code awaits/cancels in-flight executions before confirming stop so the activity gate is idle before restarting.
- If it persists, inspect the activity gate for a leaked active scope (unclosed activity registration) and file/fix the leak.
Defensive patterns
Strategy: retry
Validate before calling
// Check gate state before admission if exposed: // if (states.isClosed(scope) && gates.isActive(scope)) await waitForSettle(scope);
Try / catch
await retry(async () => admitLease(params), {
retries: 5,
factor: 2,
onRetry: (e) => { if (!/still settling cancelled work/.test(e.message)) throw e; }
}); Prevention
- Await or cancel in-flight executions before confirming a sandbox stop.
- Add automatic retry with backoff after any stop operation.
- Avoid immediately restarting a sandbox right after cancel; give it a settle window.
When it happens
Trigger: Calling lease admission/restart (the function building { providerLeaseId, config } around plugin.ts:2221) while sandboxHandleLeaseAdmissionStates.isClosed(scope) is true and sandboxHandleActivityGates.isActive(scope) is true — i.e. a stop was confirmed but an old SDK request is still active.
Common situations: A user cancels/stops work and immediately retries; a supervisor restarts a lease right after stop while a long-running exec still drains; race between stop confirmation and completion of an earlier SDK call.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Bridge response envelope changed while reading.
- CreateOS lease cleanup is already in progress.
- CreateOS lease is closing or requires cleanup.
- Plugin worker " " is not running for the duplex channel.
- Warm transition result is not yet authenticated.
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/a9d4283258d989c9.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugins/sandbox-providers/daytona/src/plugin.ts:2221
throw error;
}
},
async onEnvironmentResumeLease(
params: PluginEnvironmentResumeLeaseParams,
): Promise<PluginEnvironmentLease> {
const config = parseDriverConfig(params.config);
const scope: SandboxScope = {
driverKey: params.driverKey,
companyId: params.companyId,
environmentId: params.environmentId,
providerLeaseId: params.providerLeaseId,
config,
};
// A confirmed stop may precede completion of an old SDK request. Do not
// restart its sandbox under that request; it could still write or execute.
if (sandboxHandleLeaseAdmissionStates.isClosed(scope) && sandboxHandleActivityGates.isActive(scope)) {
throw new Error("The stopped Daytona sandbox is still settling cancelled work. Retry shortly.");
}
return await withSandboxActivityGate(scope, async () => {
const sandbox = await getSandboxOrNull(scope, { bypassTeardownGate: true });
if (!sandbox) {
return { providerLeaseId: null, metadata: { expired: true } };
}
// A stopped sandbox loses its session shell, so the stored session id is
// stale after a real restart. Clear the id only when the sandbox is not
// already running, and clear it before the restart. A stopped sandbox has
// no live session, so the clear drops a dead id and a later command opens
// a fresh session. A running sandbox keeps its live session, so the resume
// leaves the id in place; a concurrent command still finds it and teardown
// deletes one session. An unconditional clear would drop the id of a live
// session and leak its shell until sandbox reaping.
if (sandbox.state !== "started") {
sandboxHandleSessionStore.clear(scope);
// A stopped sandbox loses its pseudo-terminals, so a stored duplex channelView on GitHub (pinned to 3f1d897a7c)