{"record":{"id":"794dcc4c2b946078","repo":"paperclipai/paperclip","slug":"daytona-sandbox-handle-mismatch-handle-sandbox","errorCode":null,"errorMessage":"Daytona sandbox handle mismatch: handle ${sandbox.id} does not belong to lease ${providerLeaseId}.","messagePattern":"Daytona sandbox handle mismatch: handle (.+?) does not belong to lease (.+?)\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"packages/plugins/sandbox-providers/daytona/src/plugin.ts","lineNumber":858,"sourceCode":"    .digest(\"hex\");\n}\n\nfunction sandboxHandleCacheKey(scope: SandboxScope): string {\n  return stableStringify({\n    driverKey: scope.driverKey,\n    companyId: scope.companyId,\n    environmentId: scope.environmentId,\n    providerLeaseId: scope.providerLeaseId,\n    account: sandboxAccountDiscriminator(scope.config),\n  });\n}\n\nfunction assertHandleMatchesLease(sandbox: Sandbox, providerLeaseId: string): void {\n  // C2: a handle must never stand in for a different sandbox than the lease\n  // asked for. Belt-and-suspenders against a provider that returns a renamed or\n  // substituted sandbox, and against any future key collision.\n  if (sandbox.id !== providerLeaseId) {\n    throw new Error(\n      `Daytona sandbox handle mismatch: handle ${sandbox.id} does not belong to lease ${providerLeaseId}.`,\n    );\n  }\n}\n\n// A cached `Sandbox` carries the provider state captured when it was last\n// fetched/refreshed. Daytona auto-stops an idle sandbox after `autoStopInterval`\n// minutes, at which point that snapshot (\"started\") no longer matches reality\n// and `ensureSandboxStarted` would wrongly skip the restart, sending every\n// subsequent exec/sync at a stopped sandbox. Before reusing a handle that has\n// gone untouched for this fraction of the auto-stop interval we re-read the live\n// state so the restart decision is made against the truth. Reusing a handle for\n// an operation resets Daytona's idle clock, so an actively-used lease stays well\n// inside the window and never pays the refresh — only a lease resumed after an\n// idle gap does.\nconst STALE_HANDLE_REFRESH_SAFETY_FRACTION = 0.5;\n\nfunction staleHandleRefreshThresholdMs(autoStopIntervalMinutes: number | null): number | null {","sourceCodeStart":840,"sourceCodeEnd":876,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/plugins/sandbox-providers/daytona/src/plugin.ts#L840-L876","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"function handleMatchesLease(sandboxId: string, providerLeaseId: string): boolean {\n  return sandboxId === providerLeaseId;\n}","typeGuard":null,"tryCatchPattern":"try {\n  assertHandleMatchesLease(sandbox, providerLeaseId);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('handle mismatch')) {\n    // release and acquire a new lease; do not reuse the mismatched handle\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["daytona","sandbox","lease","security","cache","validation"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}