paperclipai/paperclip · error
Daytona sandbox handle mismatch: handle
Error message
Daytona sandbox handle mismatch: handle ${sandbox.id} does not belong to lease ${providerLeaseId}. What it means
Belt-and-suspenders identity check (C2) in assertHandleMatchesLease: the sandbox handle the provider returned (sandbox.id) does not match the lease's providerLeaseId, suggesting a renamed or substituted sandbox. The driver refuses to use a handle that stands in for a different sandbox than the lease asked for, preventing cross-lease sandbox confusion.
Solutions
- Use the sandbox handle that belongs to the lease; re-acquire the sandbox for the lease.
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at packages/plugins/sandbox-providers/daytona/src/plugin.ts:956 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-08-18).
Data as JSON: /api/errors/794dcc4c2b946078.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugins/sandbox-providers/daytona/src/plugin.ts:1005
const resolvedApiKey = config.apiKey ?? process.env.DAYTONA_API_KEY?.trim() ?? null;
return createHash("sha256")
.update(stableStringify({
apiUrl: config.apiUrl,
// Target is a creation placement hint, not account identity: the SDK
// resolves existing sandboxes by ID. Lease metadata fills an omitted
// target with the actual region, which must not split admission state.
apiKey: resolvedApiKey,
}))
.digest("hex");
}
function sandboxHandleCacheKey(scope: SandboxScope): string {
return stableStringify({
driverKey: scope.driverKey,
companyId: scope.companyId,
environmentId: scope.environmentId,
providerLeaseId: scope.providerLeaseId,
account: sandboxAccountDiscriminator(scope.config),
});
}
function assertHandleMatchesLease(sandbox: Sandbox, providerLeaseId: string): void {
// C2: a handle must never stand in for a different sandbox than the lease
// asked for. Belt-and-suspenders against a provider that returns a renamed or
// substituted sandbox, and against any future key collision.
if (sandbox.id !== providerLeaseId) {
throw new Error(
`Daytona sandbox handle mismatch: handle ${sandbox.id} does not belong to lease ${providerLeaseId}.`,
);
}
}
// A cached `Sandbox` carries the provider state captured when it was last
// fetched/refreshed. Daytona auto-stops an idle sandbox after `autoStopInterval`
// minutes, at which point that snapshot ("started") no longer matches reality
// and `ensureSandboxStarted` would wrongly skip the restart, sending everyView on GitHub (pinned to 3f1d897a7c)