paperclipai/paperclip · critical
Daytona sandbox handle mismatch: handle ${sandbox.id} does n
Error message
Daytona sandbox handle mismatch: handle ${sandbox.id} does not belong to lease ${providerLeaseId}. What it means
Thrown by assertHandleMatchesLease when a cached/resolved Daytona Sandbox handle's id does not equal the lease's providerLeaseId. This belt-and-suspenders guard prevents a handle from standing in for a different sandbox than the lease asked for, defending against a provider returning a renamed/substituted sandbox or a future key collision.
Source
Thrown at packages/plugins/sandbox-providers/daytona/src/plugin.ts:858
.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 every
// subsequent exec/sync at a stopped sandbox. Before reusing a handle that has
// gone untouched for this fraction of the auto-stop interval we re-read the live
// state so the restart decision is made against the truth. Reusing a handle for
// an operation resets Daytona's idle clock, so an actively-used lease stays well
// inside the window and never pays the refresh — only a lease resumed after an
// idle gap does.
const STALE_HANDLE_REFRESH_SAFETY_FRACTION = 0.5;
function staleHandleRefreshThresholdMs(autoStopIntervalMinutes: number | null): number | null {View on GitHub (pinned to 67001ec6eb)
Solutions
- Release the current lease and acquire a fresh sandbox to get a consistent id.
- Verify the providerLeaseId and sandbox.id match in Daytona's dashboard/API.
- Check that the cache key (driverKey/companyId/environmentId/providerLeaseId/account discriminator) is unique per lease.
- If recurring, audit sandboxAccountDiscriminator inputs (apiUrl/target/apiKey) for unintended sharing.
Defensive patterns
Strategy: validation
Validate before calling
function handleMatchesLease(sandboxId: string, providerLeaseId: string): boolean {
return sandboxId === providerLeaseId;
} Try / catch
try {
assertHandleMatchesLease(sandbox, providerLeaseId);
} catch (e) {
if (e instanceof Error && e.message.includes('handle mismatch')) {
// release and acquire a new lease; do not reuse the mismatched handle
}
throw e;
} Prevention
- Never reuse a sandbox handle across different leases.
- Ensure the cache key uniquely identifies each lease (including account discriminator).
- Treat a handle/lease id mismatch as a potential provider anomaly; acquire fresh.
When it happens
Trigger: getSandbox returns a Sandbox whose .id differs from scope.providerLeaseId, then assertHandleMatchesLease throws. Can occur if the cache key collides, the provider returns a renamed sandbox, or a stale handle is reused for a recycled lease id.
Common situations: Daytona recycled/substituted a sandbox under the same lease; a cache key collision after config changes (apiUrl/target/apiKey); manual lease id reuse across environments; a bug in cache key construction.
Related errors
- Daytona sync ${label} path is not a confined absolute path:
- Daytona sync ${label} path escapes the workspace remote dir:
- networkAllowlist[${index}] must use an exact hostname; wildc
- sync operation ${label} path is not a confined absolute path
- sync operation ${label} path escapes its confinement root: $
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/794dcc4c2b946078.
Report an issue: GitHub.