A plugin worker communicates with the host over newline-delimited JSON-RPC
on the child's stdin/stdout pipes. If the host->worker stdin pipe is
destroyed by an EPIPE (or otherwise closes) while the child process keeps
running, the worker becomes uncommandable: every `sendMessage` throws
`Worker process for plugin "<id>" is not writable`, so all host->worker
calls (e.g. http.fetch proxying) fail forever. Crucially `child.on("exit")`
never fires, so the existing crash-recovery/backoff path is never reached
and the worker silently zombies — observed in a Telegram-bridge plugin whose
getUpdates long-poll began failing after ~5 days of uptime and never
recovered until a manual disable/enable.
Fix (no polling watchdog — supervise the command channel the same way the
process is already supervised):
1. `sendMessage` passes a write callback so an async stdin write error is
surfaced/logged instead of being swallowed.
2. `attachStdioHandlers` attaches `error`/`close` handlers to `child.stdin`.
If the channel dies while the process is still alive (exitCode/signalCode
null) and the stop wasn't intentional, it SIGKILLs the child so the
normal `handleProcessExit() -> scheduleRestart()` recovery runs. This is
the missing third failure mode, mirroring the existing exit/error handlers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| scripts | ||
| src | ||
| CHANGELOG.md | ||
| package.json | ||
| tsconfig.json | ||
| vitest.config.ts | ||