paperclipai/paperclip · error

Worker process for plugin

Error message

Worker process for plugin "${pluginId}" is not writable

What it means

The JSON-RPC send path found the worker plugin's stdio channel is no longer writable (process exiting or already dead). The guard fires before attempting a write so callers learn the worker is gone rather than hitting a silent EPIPE.

Solutions

  1. The worker's stdin is closed (crashed or stopping). Restart the plugin worker and retry the call.
Defensive patterns

Strategy: retry

When it happens

Trigger: Thrown at server/src/services/plugin-worker-manager.ts:735 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/a1745a22ebdf7abe. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/plugin-worker-manager.ts:927

  // incoming line before the JSON parse and the total streamed output for one
  // execute call. A caller (a test) can lower them.
  const maxIncomingMessageChars =
    options.executeLogLimits?.maxIncomingMessageChars ?? MAX_WORKER_MESSAGE_CHARS;
  const maxExecuteLogTotalChars =
    options.executeLogLimits?.maxTotalCharsPerExecute ?? MAX_EXECUTE_LOG_TOTAL_CHARS;

  // Bounds and timeouts for the login pseudo-terminal route. A caller
  // (a test) can lower them to exercise the terminalize paths.
  const maxLoginPtyChunkChars =
    options.loginPtyLimits?.maxChunkChars ?? MAX_LOGIN_PTY_CHUNK_CHARS;
  const maxLoginPtyTotalChars =
    options.loginPtyLimits?.maxTotalChars ?? MAX_LOGIN_PTY_TOTAL_CHARS;
  const maxLoginPtyPreBindFrames =
    options.loginPtyLimits?.maxPreBindFrames ?? MAX_LOGIN_PTY_PRE_BIND_FRAMES;
  const maxLoginPtyPreBindChars =
    options.loginPtyLimits?.maxPreBindChars ?? MAX_LOGIN_PTY_PRE_BIND_CHARS;
  const loginPtyOpenTimeoutMs =
    options.loginPtyLimits?.openTimeoutMs ?? LOGIN_PTY_OPEN_TIMEOUT_MS;
  const loginPtyCloseTimeoutMs =
    options.loginPtyLimits?.closeTimeoutMs ?? LOGIN_PTY_CLOSE_TIMEOUT_MS;

  // Bounds and timeouts for the generic duplex channel route. A caller (a test)
  // can lower them to exercise each bound and the terminalize paths. Each
  // "Chars" name is a historical holdover: the channel now carries `Uint8Array`
  // chunks, so every one of these bounds counts raw bytes (`.length` on a
  // `Uint8Array` is its byte count), not UTF-16 code units.
  const maxDuplexChannelChunkChars =
    options.duplexChannelLimits?.maxChunkChars ?? MAX_DUPLEX_CHANNEL_CHUNK_CHARS;
  const maxDuplexChannelPreBindChars =
    options.duplexChannelLimits?.maxPreBindBufferedChars ??
    DUPLEX_ROUTE_INPUT_MAX_BYTES;
  const maxDuplexChannelPreBindFrames =
    options.duplexChannelLimits?.maxPreBindBufferedFrames ??
    MAX_DUPLEX_CHANNEL_PRE_BIND_FRAMES;
  // The pre-bind hold ceiling stays one frame above the buffered bound, so the
  // replay, not the hold, ends the route on the buffered bound. See

View on GitHub (pinned to 01ad858492)