{"record":{"id":"ec727a874bad4398","repo":"paperclipai/paperclip","slug":"duplex-channel-capability-denied","errorCode":"DUPLEX_CHANNEL_CAPABILITY_DENIED","errorMessage":"Sandbox lease does not grant the duplex command stream capability.","messagePattern":"Sandbox lease does not grant the duplex command stream capability\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/environment-runtime.ts","lineNumber":3546,"sourceCode":"        // every capability false.\n        return noCapabilities();\n      }\n\n      // Read the declaration from the exact plugin that acquired the lease, not\n      // the plugin the environment's current config names. A plugin uninstall,\n      // a manifest change, or a config edit between the acquire and this call\n      // must not let a stale declaration or a different plugin's worker grant a\n      // capability this lease never verified.\n      const plugin = await pluginRegistry.getById(pluginId);\n      const declaredDriver =\n        plugin && plugin.status === \"ready\"\n          ? plugin.manifestJson.environmentDrivers?.find((candidate) => candidate.driverKey === driverKey)\n          : undefined;\n      if (!plugin || plugin.status !== \"ready\" || !declaredDriver) {\n        return noCapabilities();\n      }\n\n      // Read the live worker method list fresh on every call. A worker restart\n      // can drop or add a method between the acquire and this call, so the\n      // runtime never trusts a cached method list for a capability grant.\n      const verifiedMethods = workerManager.getWorker(plugin.id)?.supportedMethods ?? [];\n      const declared = resolveDeclaredSandboxCapabilities(declaredDriver);\n      const narrowing = buildSandboxCapabilityNarrowing({\n        leasePolicy: input.lease.leasePolicy,\n        leaseMetadata: metadata,\n      });\n\n      return classifyEnvironmentCapabilities({\n        verifiedMethods,\n        declared,\n        narrowing,\n        supportedCapabilities: ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT.plugin.supportedCapabilities,\n      });\n    },\n  };\n}","sourceCodeStart":3528,"sourceCodeEnd":3564,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/environment-runtime.ts#L3528-L3564","documentation":"Authorization refusal for duplex channels, centralized in the runtime facade: before delegating to the driver it resolves the lease's effective sandbox capability snapshot via driver.effectiveSandboxCapabilities and requires the opt-in duplexCommandStream capability to be exactly true (server/src/services/environment-runtime.ts:3485, constant DUPLEX_CHANNEL_CAPABILITY_DENIED). A missing snapshot, a snapshot without the capability, or a driver that cannot resolve capabilities all fail closed — unauthorized leases never reach the worker.","triggerScenarios":"openDuplexChannel on a lease whose effective capability snapshot does not include duplexCommandStream: true — capability never opted in at lease/environment creation, revoked since acquisition, or the driver cannot resolve a snapshot (returns undefined → null → denied). Reached only after the driver-support check passed.","commonSituations":"New deployments enabling duplex-based tooling without opting leases into the capability; capability gates rolled out server-side before configurations were updated; tests using bare leases without capability metadata.","solutions":["Grant the duplexCommandStream capability in the environment/sandbox configuration the lease is derived from, then acquire a new lease.","Verify with the driver's effectiveSandboxCapabilities (or equivalent API) that the resolved snapshot shows duplexCommandStream === true before opening the channel.","If the snapshot unexpectedly resolves to null, fix the capability resolution path on the driver — absence is treated as denial by design.","Callers that do not need interactive streaming should use execute instead of requesting the capability."],"exampleFix":"// before\nawait envRuntime.openDuplexChannel({ lease, environment, command }); // DUPLEX_CHANNEL_CAPABILITY_DENIED\n\n// after (opt in at environment config / lease acquisition)\nconst lease = await acquireSandboxLease(environment, {\n  capabilities: { duplexCommandStream: true },\n});\nawait envRuntime.openDuplexChannel({ lease, environment, command });","handlingStrategy":"validation","validationCode":"const effective = (await driver.effectiveSandboxCapabilities?.({ lease, environment, command })) ?? null;\nif (effective?.duplexCommandStream !== true) throw new Error(\"Lease lacks duplexCommandStream; opt the environment/lease in before opening a channel.\");","typeGuard":"function leaseGrantsDuplex(snapshot: { duplexCommandStream?: unknown } | null | undefined): boolean {\n  return snapshot?.duplexCommandStream === true;\n}","tryCatchPattern":"try { return await runtime.openDuplexChannel(input); } catch (err) { if ((err as Error).message === \"Sandbox lease does not grant the duplex command stream capability.\") { throw new PermissionError(\"Opt the lease into duplexCommandStream and re-acquire it.\"); } throw err; }","preventionTips":["Opt leases into duplexCommandStream explicitly at acquisition; absence is denial by design.","Do not request the capability broadly — grant it only to leases that need interactive streaming.","Re-check the effective snapshot after capability config changes; leases do not gain capabilities retroactively."],"tags":["sandbox","duplex-channel","authorization","capability","permission-denied"],"backgroundTag":"capability-not-granted","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"}