Files
Mr. MakandClaude Opus 5 e64caaf372 fix(mcp): revalidate the stdio server's lifecycle, and give the PID record durable identity (#3364)
Review round 2 (@ruvnet on #3365). The first round kept a one-way "has served
stdio" flag so a second stop() could not delete another server's record. That
flag outlived the server it described: after start -> stop, an independent
status query still reported the manager running and checkHealth() still
reported it healthy. On upstream e558f0c0f the same replay reported
{"running":true,...} and {"healthy":false,"error":"No PID file found"} — the
status half is the pre-existing #2934 fallback, the health half was ours.

The flag is now the manager's live lifecycle ('idle' | 'running' | 'stopped'):

  - while it is serving stdio the manager IS the server, so it reports itself
    and never reads or clears the record it deliberately never wrote (the 30s
    health monitor calls checkHealth());
  - once stopped it reports running: false and healthy: false rather than
    whatever PID the shared file happens to hold;
  - 'idle' — a manager that never started, which is what `mcp status`,
    `mcp health` and `mcp stop` build — keeps today's behaviour exactly,
    including the #2934 stdio fallback.

stop() no longer needs a sticky flag: a stopped manager reports not running,
so a second stop() returns at its own guard.

The PID record now carries durable instance identity, the same model #3363
reached for the policy lock: host, platform, Linux PID namespace and kernel
boot id, and the owner's process start time (/proc/<pid>/stat field 22 on
Linux, `ps -o lstart=` elsewhere). A recorded PID is believed only while all
of it still matches, so a PID the OS has since handed to another process, or
one from another boot, host or namespace, is no longer treated as a live
server — it can no longer make an http start refuse, or be SIGKILLed by
`mcp start --force`. A server retracts its record only while the file still
holds, byte for byte, what it wrote.

Format compatibility: line 1 is still the bare PID, so an older ruflo's
parseInt and `head -1` both read it unchanged; line 2 is the identity. A
bare-integer record — an older ruflo's, or `start-mcp.sh --daemon`'s — parses
with no identity and behaves exactly as today. v3/scripts/start-mcp.sh now
reads the first line.

Not covered: PID reuse and boot detection need the identity line, so a record
written by an older ruflo keeps today's exposure; Windows has neither /proc
nor ps, so its records carry no start time and PID reuse stays unqualified
there, as isProcessRunning()'s process-name check already is.

Tests: 13 added (20 total in the file), covering start -> stop -> independent
status/health denial, concurrent ownership, PID reuse and non-owned processes,
and the Windows-shaped inputs. All 13 fail on 85c68888e except the two
labelled as pinning existing behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTedGmPKpEdArVBMMn3JaP
2026-09-20 11:55:49 -04:00
..
2026-01-04 22:48:29 +00:00