What happened
When the server restarts while a session still lists background tasks (typical for an agent left running overnight), startup clears those tasks and announces the change. The announcement went out while the server was still waiting to listen. publish() checked the event's environment by calling bootstrap(), which validates the whole bootstrap response, and websocketUrl is only set after listen. The ZodError was thrown inside a microtask, so it killed the process. Under node --watch the server then sat at "Waiting for file changes before restarting".
A restart is easy to trigger in development: pnpm install in any worktree relinks files in the main checkout's node_modules, and node --watch restarts on that.
Changes
- Nothing is sent before the server listens.
publishDurableEventandpublishThreadEventare bound only afterlisten. Nobody can be connected earlier, and durable events are stored and replay on reconnect. - The environment check no longer goes through
bootstrap().attachWebSockettakesenvironmentIddirectly for subscribe, replay and publish. This also stops every published event from rebuilding and validating the provider list. - A failed live send is logged, not thrown. The event is already stored, so the cost is a live delivery, not the process.
- Crashes reach the service log. When a log file is configured,
uncaughtExceptionMonitorwrites the crash and its stack there. The supervisor only records an exit code, and a headless service has no console. The monitor doesn't change how the process exits.
Tests
- New
server.test.tscase: start, stop, leave a sessionrunningwith a background task in SQLite, start again. Before the fix it fails with the samewebsocketUrlZodErrorseen in the field. After the fix it starts, serves/health, and the session readsidlewith no tasks. pnpm --filter @openmanager/server test: 780 passed, 7 skipped. Typecheck and lint are clean.
Known, not changed here
From an audit of other ways the process can die:
threadService.onRuntimeEventhas no guard. For Claude, a thrown projection error is treated as the CLI dying, so the session ends. The server stays up.- ACP child processes only listen for
'error'while spawning. - The WebSocket upgrade handler touches SQLite (audit, authenticate) with no guard.
- A throwing after-reply task on an async command becomes an unhandled rejection.
🤖 Generated with Claude Code
[Medium risk] Adjusts server startup and event handling for graceful restarts.
The PR appears safe to merge; no blocking issues remain.
Summary
Keeps the server running when startup clears background work left by the previous process.
bootstrap().No new actionable issues were found in the changes since the last review.
Diagram
Reviews (3) · Last reviewed commit: "fix(server): redact crash lines, and say..." · Reviewed by Greptile