paperclipai/paperclip · error
Sandbox driver does not support duplex channels for this…
Error message
Sandbox driver does not support duplex channels for this lease.
What it means
Thrown by the plugin-backed sandbox environment driver's openDuplexChannel when the lease was not created by a plugin sandbox provider or the server has no plugin worker manager. Duplex channels (bidirectional command streaming into a sandbox) are only implemented as a host-owned route on a plugin worker; the guard at server/src/services/environment-runtime.ts:2481 requires lease.metadata.sandboxProviderPlugin to be set and a pluginWorkerManager to exist, otherwise the operation is unsupported for that lease.
Solutions
- Verify the lease is a plugin-provider sandbox lease: its metadata must include sandboxProviderPlugin (set at provisioning time by the plugin sandbox driver).
- Ensure the server runtime was initialized with a pluginWorkerManager and the sandbox plugin is enabled in server config.
- Provision the environment through the plugin sandbox provider so leases carry the required metadata before requesting duplex channels.
- If you only need one-shot command execution, use the regular sandbox execute path which does not require a duplex channel.
Example fix
// before
const channel = await runtime.openDuplexChannel({ lease, environment, command }); // throws: driver does not support duplex channels
// after
if (!lease.metadata?.sandboxProviderPlugin) {
const result = await runtime.execute({ lease, environment, command }); // one-shot path
} else {
const channel = await runtime.openDuplexChannel({ lease, environment, command });
} Defensive patterns
Strategy: type-guard
Validate before calling
const isPluginLease = Boolean(lease.metadata?.sandboxProviderPlugin);
const pluginWorkersEnabled = Boolean(serverFeatures.pluginWorkerManager);
if (!isPluginLease || !pluginWorkersEnabled) { await runtime.execute({ lease, environment, command }); } // one-shot fallback path Type guard
function supportsPluginDuplexChannel(lease: { metadata?: Record<string, unknown> | null }): boolean {
return lease.metadata?.sandboxProviderPlugin === true || typeof lease.metadata?.sandboxProviderPlugin === "string";
} Try / catch
try { return await driver.openDuplexChannel(input); } catch (err) { if (/does not support duplex channels/.test(String((err as Error).message))) { return await runtime.execute(input); } throw err; } Prevention
- Only request duplex channels on leases provisioned by the plugin sandbox provider.
- Deploy with the plugin worker manager enabled when duplex streaming is part of the workflow.
- Gate duplex-based tooling on the lease metadata, not on best-effort attempts.
When it happens
Trigger: Calling openDuplexChannel on the environment runtime with a lease whose metadata lacks sandboxProviderPlugin (a local/docker/inline lease routed to the plugin adapter), or when the server was constructed without a pluginWorkerManager (plugin subsystem disabled) even though the lease metadata claims a plugin provider.
Common situations: Agent tooling attempts to stream commands into a sandbox that was provisioned by a non-plugin driver; the feature flag/config enabling plugin workers is off in that deployment; the lease was created before the plugin sandbox provider existed (old metadata shape).
Related errors
- Daytona duplex channel: no cached sandbox resolves the…
- Sandbox duplex channel needs a plugin id, a provider key…
- CreateOS does not yet support leases with a guaranteed…
- CreateOS execution requires a lease from this environment.
- CreateOS lease cleanup must finish before resume.
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-08-21).
Data as JSON: /api/errors/76cd561cc94f654d.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/environment-runtime.ts:2531
},
resolvePluginSandboxRpcTimeoutMs(workerConfig),
);
}
// Built-in provider path. Resolve the recorded config secrets through the
// durable orphan record, not the environment binding, for the same reason
// as the plugin path above. The teardown targets the recorded provider,
// never the current environment provider.
const cleanupConfig = await resolveSandboxCleanupConfigSecrets(
db,
input.lease.companyId,
metadataConfig,
{ issueId: input.lease.issueId, heartbeatRunId: input.lease.heartbeatRunId },
);
await destroySandboxProviderLease({
config: cleanupConfig,
providerLeaseId: input.lease.providerLeaseId,
});
},
async isPendingCleanupWorkerReady(input) {
// Resolve the recorded provider the same way `retryPendingSandboxTeardown`
// does, so the probe reads the same target the teardown uses.
const recordedProvider =
input.lease.provider ??
(typeof input.lease.metadata?.provider === "string" ? input.lease.metadata.provider : null);
// A missing provider is a permanent misconfiguration. Report ready, so the
// teardown runs, throws, and counts toward the cap.
if (!recordedProvider) return true;
// A built-in provider has no plugin worker, so it is always ready.
if (isBuiltinSandboxProvider(recordedProvider)) return true;
// No worker manager is a permanent condition here. Report ready, so the
// teardown runs, throws its own "no worker manager" error, and counts
// toward the cap.
if (!pluginWorkerManager) return true;
// Resolve the installed plugin without a wait. A plugin reload or a pluginView on GitHub (pinned to 3f1d897a7c)