{"record":{"id":"aac633ea169db74f","repo":"paperclipai/paperclip","slug":"daytona-duplex-channel-no-cached-sandbox-resolves","errorCode":null,"errorMessage":"Daytona duplex channel: no cached sandbox resolves the provider lease.","messagePattern":"Daytona duplex channel: no cached sandbox resolves the provider lease\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/daytona/src/plugin.ts","lineNumber":2862,"sourceCode":"    const scope = {\n      driverKey: params.driverKey,\n      companyId: params.companyId,\n      environmentId: params.environmentId,\n      providerLeaseId: params.lease.providerLeaseId,\n      config,\n    };\n    // Collect the advisory read-write destinations for this scope. This records\n    // intent only; it does not change the transfer below.\n    sandboxHandleWritableDirs.recordWritableTargets(scope, params.operations);\n    return await withSandboxActivityGate(scope, async () => {\n      const sandbox = await getSandbox(scope, { bypassTeardownGate: true });\n      await ensureSandboxStarted(sandbox, timeoutSeconds);\n      const result = await performSyncIn({\n        sandbox,\n        operations: params.operations,\n        remoteDir,\n        timeoutSeconds,\n      });\n      sandboxHandleCache.markFresh(scope);\n      return result;\n    });\n  },\n\n  // Opt-in native outbound transfer. See onEnvironmentSyncIn.\n  async onEnvironmentSyncOut(\n    params: PluginEnvironmentSyncOutParams,\n  ): Promise<PluginEnvironmentSyncResult> {\n    if (!params.lease.providerLeaseId) {\n      throw new Error(\"Daytona syncOut requires a provider lease ID.\");\n    }\n    const config = parseDriverConfig(params.config);\n    const remoteDir = resolveSyncRemoteDir(params.lease);\n    const timeoutSeconds = toTimeoutSeconds(config.timeoutMs);\n    const scope = {\n      driverKey: params.driverKey,\n      companyId: params.companyId,","sourceCodeStart":2844,"sourceCodeEnd":2880,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/plugins/sandbox-providers/daytona/src/plugin.ts#L2844-L2880","documentation":"Thrown by the Daytona provider's onDuplexChannelOpen when sandboxHandleCache.findByProviderLeaseId(providerLeaseId) returns nothing. The duplex channel must run on a sandbox this plugin process previously created and cached; a lease with no cached sandbox means the channel cannot be opened safely, so the provider fails closed instead of guessing.","triggerScenarios":"The host sends a duplexChannelOpen RPC for a lease that was never provisioned in this process, whose sandbox entry was evicted after removal/expiry, or after a plugin/server restart that cleared the in-memory sandboxHandleCache while the host still routes by the old lease id.","commonSituations":"Sandbox deleted or expired on the Daytona side between lease grant and channel open; server restart or plugin reload losing cache state; multiple server replicas where the channel-open lands on a process that never created the sandbox; stale host-side routing metadata after a lease rotation.","solutions":["Re-provision the sandbox (acquire a fresh provider lease) and retry the channel open on the process that owns it.","If the server restarted, re-run sandbox creation so the cache repopulates before any duplex channel open.","Check the Daytona sandbox for the lease still exists; if it was removed, let the lease expire and create a new one.","Ensure channel-open requests route to the same server/plugin process that handled sandbox creation (single-owner routing)."],"exampleFix":"// before\nconst session = await openDuplexChannelSession(sandbox.process, params.command);\n\n// after (host-side guard before opening)\nif (!(await sandboxHandleCache.findByProviderLeaseId(leaseId))) {\n  await reProvisionSandbox(leaseId); // recreate sandbox + cache entry, then retry open\n}","handlingStrategy":"try-catch","validationCode":"// Host side, before requesting the channel:\nconst cached = await sandboxHandleCache.findByProviderLeaseId(providerLeaseId);\nif (!cached) {\n  await reProvisionSandbox(providerLeaseId); // recreate sandbox, then open the channel\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await plugin.definition.onDuplexChannelOpen?.(params);\n} catch (error) {\n  if (error instanceof Error && error.message.includes(\"no cached sandbox resolves the provider lease\")) {\n    // lease/cache skew: re-provision the sandbox on THIS process, then retry once\n    await provisionSandboxForLease(params.providerLeaseId);\n    return await plugin.definition.onDuplexChannelOpen?.(params);\n  }\n  throw error;\n}","preventionTips":["Route duplexChannelOpen to the same process that created the sandbox.","Re-acquire leases after server or plugin restarts before reusing cached route ids.","Treat sandbox removal/expiry events as invalidations of pending channel opens.","Do not disable the fail-closed check; a wrong sandbox would leak the channel to another tenant."],"tags":["daytona","sandbox","duplex-channel","cache-miss","lease"],"backgroundTag":"stale-resource-handle","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-08-21T17:58:32.592Z","contentChangedAt":"2026-08-21T17:58:32.592Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}