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
- Check plugin worker status on the server (plugin registry/manager logs) for the named pluginId.
- Restart or re-enable the plugin so its worker boots, then retry the channel open.
- If the worker keeps dying, inspect the plugin worker logs for the crash cause and fix the plugin before retrying.
- 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
- Ensure plugin workers are started (or eagerly warmed) before agents begin duplex sessions.
- Monitor plugin worker health and auto-restart crashed workers.
- Acquire fresh leases after plugin upgrades rather than reusing leases bound to a dead worker session.
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
- DUPLEX_CHANNEL_OPEN_FAILED
- Daytona duplex channel: no cached sandbox resolves the…
- DUPLEX_CHANNEL_CAPABILITY_DENIED
- DUPLEX_CHANNEL_ROUTE_BUSY
- Sandbox driver does not support duplex channels for this…
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)