{"record":{"id":"76cd561cc94f654d","repo":"paperclipai/paperclip","slug":"sandbox-driver-does-not-support-duplex-channels-fo","errorCode":null,"errorMessage":"Sandbox driver does not support duplex channels for this lease.","messagePattern":"Sandbox driver does not support duplex channels for this lease\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/environment-runtime.ts","lineNumber":2531,"sourceCode":"          },\n          resolvePluginSandboxRpcTimeoutMs(workerConfig),\n        );\n      }\n\n      // Built-in provider path. Resolve the recorded config secrets through the\n      // durable orphan record, not the environment binding, for the same reason\n      // as the plugin path above. The teardown targets the recorded provider,\n      // never the current environment provider.\n      const cleanupConfig = await resolveSandboxCleanupConfigSecrets(\n        db,\n        input.lease.companyId,\n        metadataConfig,\n        { issueId: input.lease.issueId, heartbeatRunId: input.lease.heartbeatRunId },\n      );\n      await destroySandboxProviderLease({\n        config: cleanupConfig,\n        providerLeaseId: input.lease.providerLeaseId,\n      });\n    },\n\n    async isPendingCleanupWorkerReady(input) {\n      // Resolve the recorded provider the same way `retryPendingSandboxTeardown`\n      // does, so the probe reads the same target the teardown uses.\n      const recordedProvider =\n        input.lease.provider ??\n        (typeof input.lease.metadata?.provider === \"string\" ? input.lease.metadata.provider : null);\n      // A missing provider is a permanent misconfiguration. Report ready, so the\n      // teardown runs, throws, and counts toward the cap.\n      if (!recordedProvider) return true;\n      // A built-in provider has no plugin worker, so it is always ready.\n      if (isBuiltinSandboxProvider(recordedProvider)) return true;\n      // No worker manager is a permanent condition here. Report ready, so the\n      // teardown runs, throws its own \"no worker manager\" error, and counts\n      // toward the cap.\n      if (!pluginWorkerManager) return true;\n      // Resolve the installed plugin without a wait. A plugin reload or a plugin","sourceCodeStart":2513,"sourceCodeEnd":2549,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/environment-runtime.ts#L2513-L2549","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"// before\nconst channel = await runtime.openDuplexChannel({ lease, environment, command }); // throws: driver does not support duplex channels\n\n// after\nif (!lease.metadata?.sandboxProviderPlugin) {\n  const result = await runtime.execute({ lease, environment, command }); // one-shot path\n} else {\n  const channel = await runtime.openDuplexChannel({ lease, environment, command });\n}","handlingStrategy":"type-guard","validationCode":"const isPluginLease = Boolean(lease.metadata?.sandboxProviderPlugin);\nconst pluginWorkersEnabled = Boolean(serverFeatures.pluginWorkerManager);\nif (!isPluginLease || !pluginWorkersEnabled) { await runtime.execute({ lease, environment, command }); } // one-shot fallback path","typeGuard":"function supportsPluginDuplexChannel(lease: { metadata?: Record<string, unknown> | null }): boolean {\n  return lease.metadata?.sandboxProviderPlugin === true || typeof lease.metadata?.sandboxProviderPlugin === \"string\";\n}","tryCatchPattern":"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; }","preventionTips":["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."],"tags":["sandbox","duplex-channel","plugin","lease","unsupported-operation"],"backgroundTag":"unsupported-driver-operation","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"}