paperclipai/paperclip · error

Sandbox duplex channel needs a plugin id, a provider key, an

Error message

Sandbox duplex channel needs a plugin id, a provider key, and a provider lease id on the lease.

What it means

Thrown by the plugin sandbox driver's openDuplexChannel when the lease metadata is incomplete: it needs readString(lease.metadata.pluginId), readString(lease.metadata.provider), and lease.providerLeaseId to address the correct plugin worker and provider session. Any of these missing or non-string produces this fixed error before any worker call is made (server/src/services/environment-runtime.ts:2487).

Source

Thrown at server/src/services/environment-runtime.ts:2487

    },

    async syncOut(input) {
      return await callPluginEnvironmentSync("environmentSyncOut", input);
    },

    async openDuplexChannel(input) {
      // Plugin-backed sandbox providers only: open the host-owned duplex route on
      // the plugin worker. The lease scope mirrors the sandbox execute path — the
      // provider driver key, the company, the environment, and the provider lease
      // id — so the route binds to the same worker session the runner streams.
      if (!input.lease.metadata?.sandboxProviderPlugin || !pluginWorkerManager) {
        throw new Error("Sandbox driver does not support duplex channels for this lease.");
      }
      const pluginId = readString(input.lease.metadata?.pluginId);
      const providerKey = readString(input.lease.metadata?.provider);
      const providerLeaseId = readString(input.lease.providerLeaseId);
      if (!pluginId || !providerKey || !providerLeaseId) {
        throw new Error(
          "Sandbox duplex channel needs a plugin id, a provider key, and a provider lease id on the lease.",
        );
      }
      const worker = pluginWorkerManager.getWorker(pluginId);
      if (!worker) {
        throw new Error(`Plugin worker "${pluginId}" is not running for the duplex channel.`);
      }
      const managerInput: WorkerManagerDuplexChannelOpenInput = {
        driverKey: providerKey,
        companyId: input.lease.companyId,
        environmentId: input.environment.id,
        providerLeaseId,
        command: input.command,
      };
      const session = await worker.openDuplexChannel(managerInput);
      return adaptDuplexChannelHostSession(session);
    },

View on GitHub (pinned to a7e689b3c3)

Solutions

  1. Inspect lease.metadata for pluginId and provider keys and lease.providerLeaseId — confirm all three are present strings.
  2. Re-acquire the sandbox lease from the plugin provider so the full metadata shape is written by the current driver version.
  3. Update the sandbox plugin and server together so metadata keys stay in sync.
  4. If providerLeaseId is the missing one, ensure the provider-side lease was granted before the channel open is attempted.

Example fix

// before
await driver.openDuplexChannel({ lease, environment, command }); // throws: needs plugin id, provider key, provider lease id

// after
const ok = typeof lease.metadata?.pluginId === "string"
  && typeof lease.metadata?.provider === "string"
  && typeof lease.providerLeaseId === "string";
if (!ok) lease = await acquireFreshPluginLease(environment);
await driver.openDuplexChannel({ lease, environment, command });
Defensive patterns

Strategy: validation

Validate before calling

const { pluginId, provider } = (lease.metadata ?? {}) as { pluginId?: unknown; provider?: unknown };
if (typeof pluginId !== "string" || typeof provider !== "string" || typeof lease.providerLeaseId !== "string") { lease = await acquireFreshPluginSandboxLease(environment); }

Type guard

function hasDuplexLeaseIdentity(lease: { metadata?: Record<string, unknown> | null; providerLeaseId?: string | null }): boolean {
  return typeof lease.metadata?.pluginId === "string"
    && typeof lease.metadata?.provider === "string"
    && typeof lease.providerLeaseId === "string";
}

Try / catch

try { await openDuplex(input); } catch (err) { if (/needs a plugin id, a provider key/.test((err as Error).message)) { input.lease = await acquireFreshPluginSandboxLease(env); await openDuplex(input); } else throw err; }

Prevention

When it happens

Trigger: openDuplexChannel on a lease that has sandboxProviderPlugin set but whose metadata lacks pluginId or provider, or whose providerLeaseId is unset — e.g. the provider driver stored metadata under different keys, the lease was deserialized from an older shape, or the provider lease handshake never completed.

Common situations: Plugin sandbox provider version mismatch (metadata keys renamed between plugin versions); a lease restored/persisted from an older release losing providerLeaseId; partially initialized lease where the provider grant step failed silently.

Related errors


AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-21). Data as JSON: /api/errors/2cc91d4b712d79f6. Report an issue: GitHub.