{"record":{"id":"ba48ba9ddad8fdf1","repo":"paperclipai/paperclip","slug":"worker-for-plugin-pluginid-is-already-statu","errorCode":null,"errorMessage":"Worker for plugin \"${pluginId}\" is already ${status}","messagePattern":"Worker for plugin \"(.+?)\" is already (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/plugin-worker-manager.ts","lineNumber":2524,"sourceCode":"      // The worker returned a pair that is already live or already tombstoned. Fail\n      // closed: terminalize this route and retire the worker before any reuse.\n      await terminalizeDuplexChannelRoute(route);\n      retireDuplexWorkerOnViolation(\"duplicate bound pair\");\n      throw new Error(DUPLEX_CHANNEL_OPEN_FAILED);\n    }\n    // Bind the worker session identifier one time and move the route from the\n    // opening map to the live map under the exact pair key.\n    route.workerSessionId = workerSessionId;\n    route.state = \"open\";\n    openingDuplexRoutes.delete(hostRouteId);\n    liveDuplexRoutes.set(pairKey, route);\n\n    // Start the route lifetime timer now the route is open. The route ends when\n    // the timer expires. Every terminal path and the worker-exit path clears the\n    // timer. Unreference the timer so it never blocks the host process shutdown.\n    route.lifetimeTimer = setTimeout(() => {\n      void terminalizeDuplexChannelRoute(route);\n    }, maxDuplexChannelDurationMs);\n    route.lifetimeTimer.unref?.();\n\n    // Replay any frame that arrived before the bind. The route is live now, so the\n    // exact-pair routing delivers each held frame or fails closed on a mismatch.\n    replayPreBindDuplexFrames(route);\n\n    // Send one host→worker request under the pending-request bound. End the route\n    // when too many requests are in-flight, so a worker that never replies cannot\n    // make the host hold an unbounded number of pending requests.\n    const sendBoundedRequest = <\n      M extends \"duplexChannelWrite\" | \"duplexChannelStop\",\n    >(\n      method: M,\n      params: HostToWorkerMethods[M][0],\n      // The exact raw byte count of a `duplexChannelWrite` payload, before base64\n      // encoding. The caller supplies it: `params.data` on the wire is the base64\n      // form (`ChannelBytesWireValue`), so its string length no longer equals the\n      // real payload bytes. A stop request carries no payload and omits this.","sourceCodeStart":2506,"sourceCodeEnd":2542,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/plugin-worker-manager.ts#L2506-L2542","documentation":"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.","triggerScenarios":"Thrown at server/src/services/plugin-worker-manager.ts:1717 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["The worker is already in the requested state; check current status before issuing start/stop transitions and skip redundant transitions."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-08-18T22:49:45.177Z","contentChangedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}