# The MCP server wedged the IDE on a list — head-of-line blocking, and why killing the process was the only release (RFC O/P/R)

> Source: <https://loomcycle.dev/blog/the-mcp-server-wedged-the-ide-on-a-list.html>
> Published: 2026-06-06 16:00:00+00:00

Day two of the operator-via-MCP series. Mid-experiment, the operator's IDE (a fresh Claude Code session driving loomcycle through the plugin's stdio MCP server) hung on a list_runs tool confirmation. The user approved the call. Nothing happened. The only way out was to kill the loomcycle mcp process from another terminal. Source-reading v0.22.0's internal/api/mcp/server.go found a single load-bearing footnote (lines 102-115): "Frames are dispatched SEQUENTIALLY on a single goroutine. Concurrent tools/call is a v0.9.x optimisation — not implemented." The comment was honest; it was also wrong about the priority. Combined with an unbounded spawn_run handler (no per-call timeout), one slow run blocked every subsequent frame in the OS pipe behind it — even a cheap list_runs, even a cancel_run. Classic head-of-line blocking on a single-consumer stream. The list was the victim, not the cause. Three amplifiers turned "slow" into "wedged for an hour": F15 (cross-runtime interruption wake) made an interruption-held run block to the 1-hour interruption.default_timeout_ms; provider outages (the day this happened coincided with an Opus 4.7/4.8 incident on Anthropic's side) stalled spawn_run on retries; F16 accumulated SSE clients + heartbeating runs added resource pressure on top. Why killing the process was the only release: ctx cancellation is polled only between frames, so cancelling the parent context doesn't preempt an in-flight handler. A cancel_run frame would itself be HOL-blocked behind the occupier. SIGTERM on the loomcycle mcp PID ends the process; stdin closes; the wedged call dies with it; the IDE sees the MCP transport drop and reconnects. Three coordinated PRs in v0.23.0 close the failure class at three levels: RFC O (#377) makes stdio dispatch concurrent on bounded goroutines (the writeMu for stdout framing already existed, only used by the notification path; now it serializes every response). Independent tool calls run concurrently; initialize/initialized stay sequential. The HOL class is gone. RFC P (#380) wraps spawn_run in a transport-level context.WithTimeout, configurable per operator. A provider stall or unkillable run no longer keeps the handler alive forever. Composed with RFC O, the bound is hard. RFC R (#381) ships the thin-client topology: loomcycle mcp --upstream launches a stdio MCP server that proxies to a single full runtime over HTTP/gRPC. Every tool call lands on the one runtime that owns the in-process bus; the cross-process bus-Notify problem dissolves (interruption_resolve from a thin client hits the same in-process bus the blocking ask waits on). F15 stops being reachable in normal use. Breaking change in the same release (#383): loomcycle mcp --no-http is removed — the pattern that needed it (two full runtimes side-by-side with HTTP suppressed on one) is the pattern that caused F15. A real fix for the original code path — durable cross-runtime wake via the _system/interrupts/resolved channel — shipped two days later (#400) for multi-host HA topologies that genuinely need multiple full runtimes. The thin-client pattern stays the recommended default. None of the three PRs would have been sufficient alone: concurrent dispatch without timeout still allows a goroutine slot to be tied up indefinitely; timeout without concurrent dispatch still produces "list hung for 5 minutes" before the timeout fires; thin-client without the other two still HOL-blocks a single transport on a single runtime. The three together remove the failure class at three different levels. The engineering lesson: a "we'll fix it later" comment on a load-bearing concurrency property is a P1 bug, not a roadmap item. The runtime documentation said the limit; the operators' code didn't read the documentation. The first real workload to touch the limit looked like complete system failure.
