paperclipai/paperclip · warning
DUPLEX_CHANNEL_ROUTE_BUSY
DUPLEX_CHANNEL_ROUTE_BUSY
Error message
DUPLEX_CHANNEL_ROUTE_BUSY
What it means
Thrown by the plugin worker manager's openDuplexChannel when a duplex channel route already exists for the worker: each worker supports exactly one live generic duplex channel route, reserved before the open call and released only after close is confirmed (server/src/services/plugin-worker-manager.ts:1796, DUPLEX_CHANNEL_ROUTE_BUSY). A second open before the previous route reached its terminal state is rejected with this fixed, non-secret error before anything is sent to the worker.
Source
Thrown at server/src/services/plugin-worker-manager.ts:1796
route.buffered = [];
route.bufferedChars = 0;
route.preOpen = [];
route.preOpenExit = null;
clearDuplexChannelLifetimeTimer(route);
settleRouteWait(route, { exitCode: null });
}
// Open one live generic duplex channel route. Reserve the route before the open
// call, bind the worker session identifier one time on the first successful
// open reply, and return a session a caller drives. Terminalize the route on
// every open failure path.
async function openDuplexChannel(
input: DuplexChannelOpenInput,
): Promise<DuplexChannelHostSession> {
if (duplexChannelRoute) {
// A route for this worker is not yet closed and confirmed. Reject the
// second open with one fixed non-secret error before it reaches the worker.
throw new Error(DUPLEX_CHANNEL_ROUTE_BUSY);
}
const hostRouteId = randomUUID();
let settleWait: (value: { exitCode: number | null }) => void = () => {};
const waitPromise = new Promise<{ exitCode: number | null }>((resolve) => {
settleWait = resolve;
});
const route: DuplexChannelRoute = {
hostRouteId,
state: "reserved",
workerSessionId: null,
listener: null,
buffered: [],
bufferedChars: 0,
preOpen: [],
preOpenExit: null,
pendingRequests: 0,
protocolErrors: 0,
totalDataBytes: 0,View on GitHub (pinned to a7e689b3c3)
Solutions
- Close and await the previous channel session (session close/terminal confirmation) before opening a new one.
- Serialize channel opens per worker with a queue/mutex so only one route is live at a time.
- If the previous session is unrecoverable, wait for its lifetime timer to expire and terminalize the route, then retry the open.
- Design reconnect paths to reuse the existing session instead of opening a second route.
Example fix
// before const ch2 = await worker.openDuplexChannel(input); // DUPLEX_CHANNEL_ROUTE_BUSY while ch1 open // after await ch1.close(); // route terminalized and confirmed const ch2 = await worker.openDuplexChannel(input);
Defensive patterns
Strategy: retry
Validate before calling
if (activeChannelForWorker(pluginId)) { await activeChannelForWorker(pluginId).close(); } // one live route per worker — close before open Try / catch
try { return await worker.openDuplexChannel(input); } catch (err) { if ((err as Error).message === "DUPLEX_CHANNEL_ROUTE_BUSY") { await existingSession.close(); return await worker.openDuplexChannel(input); } throw err; } Prevention
- Maintain exactly one duplex session per plugin worker; track it in the caller.
- Always await session close before reconnecting after failures.
- Serialize opens per worker (mutex/queue) in multi-caller code.
When it happens
Trigger: Calling openDuplexChannel twice on the same plugin worker without closing the first session — e.g. a client reconnects (dropped connection, retry loop) while the old route is still open/opening, or two callers stream into the same plugin concurrently.
Common situations: Reconnect logic that opens a new channel on transient failure without terminating the previous one; a leaked session whose close was never awaited; concurrent agents both granted duplex on the same plugin worker.
Related errors
- Plugin worker "${pluginId}" is not running for the duplex ch
- DUPLEX_CHANNEL_OPEN_FAILED
- Cannot seed target embedded PostgreSQL at ${dataDir} while i
- Worktree seed source diagnostics changed while waiting for t
- Failed to create a pending worktree seed manifest.
AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-21).
Data as JSON: /api/errors/7baef7eafb8d334b.
Report an issue: GitHub.