paperclipai/paperclip · error · Error
Daytona duplex channel: no cached sandbox resolves the provi
Error message
Daytona duplex channel: no cached sandbox resolves the provider lease.
What it means
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.
Source
Thrown at packages/plugins/sandbox-providers/daytona/src/plugin.ts:2808
// terminal is closed. The worker never keys the close on the worker session id.
async onSetupTokenPtyClose(params) {
const entry = daytonaSetupTokenPtyByRoute.get(params.hostRouteId);
if (entry) {
forgetDaytonaSetupTokenPty(entry);
await entry.session.close().catch(() => undefined);
}
return { hostRouteId: params.hostRouteId };
},
// Open one persistent duplex channel. Resolve the cached sandbox by the provider
// lease id, run the gateway command on a raw pseudo-terminal, and register the
// channel under the host route id. Stream the raw data and the exit through
// `ctx.duplexChannel`, bound to the returned worker session id. Fail closed when
// no cached sandbox matches the lease.
async onDuplexChannelOpen(params) {
const sandbox = await sandboxHandleCache.findByProviderLeaseId(params.providerLeaseId);
if (!sandbox) {
throw new Error(
"Daytona duplex channel: no cached sandbox resolves the provider lease.",
);
}
const session = await openDuplexChannelSession(
sandbox.process as unknown as DaytonaPtyProcess,
params.command,
);
const workerSessionId = `duplex-${randomUUID()}`;
const entry: DaytonaDuplexChannelEntry = {
hostRouteId: params.hostRouteId,
workerSessionId,
providerLeaseId: params.providerLeaseId,
session,
};
daytonaDuplexChannelByRoute.set(params.hostRouteId, entry);
daytonaDuplexChannelBySession.set(workerSessionId, entry);
// Register the data listener before the first write, so no early data chunk is
// lost. The client stamps the worker session id, so the host binds the data toView on GitHub (pinned to a7e689b3c3)
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).
Example fix
// before
const session = await openDuplexChannelSession(sandbox.process, params.command);
// after (host-side guard before opening)
if (!(await sandboxHandleCache.findByProviderLeaseId(leaseId))) {
await reProvisionSandbox(leaseId); // recreate sandbox + cache entry, then retry open
} Defensive patterns
Strategy: try-catch
Validate before calling
// Host side, before requesting the channel:
const cached = await sandboxHandleCache.findByProviderLeaseId(providerLeaseId);
if (!cached) {
await reProvisionSandbox(providerLeaseId); // recreate sandbox, then open the channel
} Try / catch
try {
return await plugin.definition.onDuplexChannelOpen?.(params);
} catch (error) {
if (error instanceof Error && error.message.includes("no cached sandbox resolves the provider lease")) {
// lease/cache skew: re-provision the sandbox on THIS process, then retry once
await provisionSandboxForLease(params.providerLeaseId);
return await plugin.definition.onDuplexChannelOpen?.(params);
}
throw error;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Sandbox driver does not support duplex channels for this lea
- Failed to stop Daytona sandbox during lease release: ${forma
- The duplex channel launch command needs at least one argumen
- Sandbox duplex channel needs a plugin id, a provider key, an
- Plugin worker "${pluginId}" is not running for the duplex ch
AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-21).
Data as JSON: /api/errors/aac633ea169db74f.
Report an issue: GitHub.