paperclipai/paperclip · error

Plugin worker " " is not running for the duplex channel.

Error message

Plugin worker "${pluginId}" is not running for the duplex channel.

What it means

Thrown by the plugin sandbox driver's openDuplexChannel when pluginWorkerManager.getWorker(pluginId) returns null — the lease names a plugin whose worker process is not currently running on the host. The duplex route must bind to the same worker session the sandbox runner streams through, so a stopped/crashed/never-started worker is a hard failure for the channel open.

Solutions

  1. Check plugin worker status on the server (plugin registry/manager logs) for the named pluginId.
  2. Restart or re-enable the plugin so its worker boots, then retry the channel open.
  3. If the worker keeps dying, inspect the plugin worker logs for the crash cause and fix the plugin before retrying.
  4. If the lease outlived a plugin upgrade, acquire a fresh sandbox lease from the current plugin version.
Defensive patterns

Strategy: retry

Validate before calling

const worker = pluginWorkerManager.getWorker(pluginId);
if (!worker) { await pluginWorkerManager.ensureWorkerStarted(pluginId); } // start the worker before opening the channel

Try / catch

try { return await driver.openDuplexChannel(input); } catch (err) { if (/is not running for the duplex channel/.test((err as Error).message)) { await restartPluginWorker(pluginId); return await driver.openDuplexChannel(input); } throw err; }

Prevention

When it happens

Trigger: openDuplexChannel with valid lease metadata while the plugin's worker process is down: the plugin was disabled or failed to boot, the worker crashed earlier and was not restarted, or the channel open races worker startup right after server boot.

Common situations: Plugin worker crashed (OOM, unhandled error) leaving leases valid but workers dead; server restarted and lazy worker start has not happened yet for that plugin; plugin uninstalled/updated between lease creation and channel open.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-08-21). Data as JSON: /api/errors/60551e42af0b5de7. Report an issue: GitHub.

Appendix: source

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

        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 plugin
      // reinstall can remove the plugin row for a short window, so a missing
      // plugin is a transient condition, not a permanent one. Report not ready,
      // so the sweep skips the lease without a claim, and a later sweep retries
      // after the plugin returns. A teardown while the plugin is missing only
      // throws and burns a finite attempt, so a long reload could exhaust the
      // retries and strand the sandbox. A permanent uninstall also cannot tear
      // the sandbox down, because there is no worker to call, so the preserved
      // pending_cleanup row still holds the durable cleanup state for later.
      const installed = await resolvePluginSandboxProviderDriverByKey({
        db,
        driverKey: recordedProvider,
        workerManager: pluginWorkerManager,

View on GitHub (pinned to 3f1d897a7c)