You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
119 `ouroboros mcp serve` processes on one machine; 54 holding `~/.ouroboros/data/ouroboros.db` open → SQLite write-lock contention → per-process `QueuePool limit of size 5 overflow 10 reached` (714 occurrences) → run failures.
Zombies' fd 0/1/2 are unix sockets with a dead peer (`->(none)` in lsof): the client died, the server never exits. Reproduced nightly (29 zombies from one evening).
One streamable-http serve orphaned to launchd ran 5 days at 40–80% CPU (4,263 CPU-minutes).
Root causes (audited on main @ v0.52.0)
The shutdown EOF trick `os.close(0)` (cli/commands/mcp.py:978-981) is a no-op under mcp==2.0.0: the SDK diverts fd 0 to /dev/null and reads the wire from a private dup'd fd (mcp/server/stdio.py).
The stdin readline runs in a shielded, non-daemon anyio worker thread; even after the serve coroutine returns, the interpreter blocks forever in threading shutdown. `os._exit` is explicitly forbidden (mcp.py:967-969), so there is no escape hatch.
`_orphan_watchdog` disables itself entirely when `orig_ppid == 1` (mcp.py:873-874) — a launchd-orphaned server has no lifecycle tether at all. streamable-http additionally has no idle shutdown.
CPU runaway: `JobManager._monitor_job` polls at 1 Hz and `get_snapshot` replays the job's events from row 0 on every call (mcp/job_manager.py:888, 1970), with no backoff.
Fix plan
PR A: hard-exit backstop after the shutdown grace period + dead-peer (MSG_PEEK) and `getppid()==1` detection in the watchdog.
PR B: idle shutdown for streamable-http + incremental cursor / backoff for the job monitor loops.
Symptom (production, 2026-08-31)
Root causes (audited on main @ v0.52.0)
Fix plan