paperclipai/paperclip · error
Worker for plugin "${pluginId}" is already ${status}
Error message
Worker for plugin "${pluginId}" is already ${status} What it means
startInternal was called while the plugin worker is already in 'running' or 'starting' status. The state guard prevents double-start races from spawning duplicate worker processes for the same plugin.
Source
Thrown at server/src/services/plugin-worker-manager.ts:2524
// The worker returned a pair that is already live or already tombstoned. Fail
// closed: terminalize this route and retire the worker before any reuse.
await terminalizeDuplexChannelRoute(route);
retireDuplexWorkerOnViolation("duplicate bound pair");
throw new Error(DUPLEX_CHANNEL_OPEN_FAILED);
}
// Bind the worker session identifier one time and move the route from the
// opening map to the live map under the exact pair key.
route.workerSessionId = workerSessionId;
route.state = "open";
openingDuplexRoutes.delete(hostRouteId);
liveDuplexRoutes.set(pairKey, route);
// Start the route lifetime timer now the route is open. The route ends when
// the timer expires. Every terminal path and the worker-exit path clears the
// timer. Unreference the timer so it never blocks the host process shutdown.
route.lifetimeTimer = setTimeout(() => {
void terminalizeDuplexChannelRoute(route);
}, maxDuplexChannelDurationMs);
route.lifetimeTimer.unref?.();
// Replay any frame that arrived before the bind. The route is live now, so the
// exact-pair routing delivers each held frame or fails closed on a mismatch.
replayPreBindDuplexFrames(route);
// Send one host→worker request under the pending-request bound. End the route
// when too many requests are in-flight, so a worker that never replies cannot
// make the host hold an unbounded number of pending requests.
const sendBoundedRequest = <
M extends "duplexChannelWrite" | "duplexChannelStop",
>(
method: M,
params: HostToWorkerMethods[M][0],
// The exact raw byte count of a `duplexChannelWrite` payload, before base64
// encoding. The caller supplies it: `params.data` on the wire is the base64
// form (`ChannelBytesWireValue`), so its string length no longer equals the
// real payload bytes. A stop request carries no payload and omits this.View on GitHub (pinned to 01ad858492)
Solutions
- The worker is already in the requested state; check current status before issuing start/stop transitions and skip redundant transitions.
Defensive patterns
Strategy: type-guard
When it happens
Trigger: Thrown at server/src/services/plugin-worker-manager.ts:1717 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-08-18).
Data as JSON: /api/errors/ba48ba9ddad8fdf1.
Report an issue: GitHub.