Skip to content

fix(mcp): wire_send uses a cheap pidfile liveness check, not the full scan (#350)#353

Merged
laulpogan merged 1 commit into
mainfrom
fix/mcp-send-cheap-daemon-check
Jun 20, 2026
Merged

fix(mcp): wire_send uses a cheap pidfile liveness check, not the full scan (#350)#353
laulpogan merged 1 commit into
mainfrom
fix/mcp-send-cheap-daemon-check

Conversation

@laulpogan

Copy link
Copy Markdown
Collaborator

Willard reported painfully slow MCP on Windows. Root cause confirmed in code, not theory.

The bug

tool_send — i.e. every wire_send, the primary MCP action — called daemon_liveness() purely to populate one diagnostic boolean (daemon_seen = snap.pidfile_alive). But daemon_liveness() also runs the machine-wide orphan scan the send path never reads:

On Unix that's a cheap pgrep + /proc; on Windows it's seconds, paid on every send. (The code comment even called these annotations "cheap to compute" — true on Unix, false on Windows.)

Fix

New ensure_up::daemon_pidfile_alive() — one daemon.pid read + one pid_is_alive (one tasklist on Windows) → the identical daemon_seen boolean for a fraction of the cost. Both tool_send branches use it. wire_status keeps full daemon_liveness() (it legitimately reports orphans).

Scope / verification

🤖 Generated with Claude Code

… scan (#350)

Willard reported painfully slow MCP on Windows. Root cause confirmed in code:
`tool_send` (every `wire_send`, the primary MCP action) called
`daemon_liveness()` purely to read one diagnostic boolean (`daemon_seen` =
`snap.pidfile_alive`). But `daemon_liveness()` also runs the machine-wide
orphan-detection scan the send path never uses:
- `find_processes_by_cmdline("wire daemon")` → PowerShell `Get-CimInstance` on
  Windows (enumerates ALL processes, ~1-3s cold),
- `list_sessions()` → reads `agent-card.json` from EVERY by-key home (Willard's
  box has hundreds — see #351's session proliferation), each an NTFS dir read,
- `pid_is_alive` per session daemon pid → a `tasklist.exe` spawn each.

On Unix that's a cheap `pgrep` + `/proc`; on Windows it's seconds — paid on
every send. The annotation only needs `daemon_seen`.

Fix: new `ensure_up::daemon_pidfile_alive()` — one `daemon.pid` read + one
`pid_is_alive` (one `tasklist` on Windows) — yielding the identical boolean.
`tool_send`'s two branches use it instead of full `daemon_liveness()`.
`wire_status` keeps the full scan (it legitimately reports orphans).

Cross-platform; Windows CI (`install-smoke-windows`) compile-verifies. The
deeper Windows wins (native Win32 to kill the remaining PowerShell/tasklist/reg
spawns, #350) and the MCP/CLI identity split (#351) remain tracked; this removes
the per-send hot-path scan, the dominant cost.

Test `daemon_pidfile_alive_false_without_pidfile`. 610 lib tests; fmt+clippy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying wireup-landing with  Cloudflare Pages  Cloudflare Pages

Latest commit: 9a2f63d
Status: ✅  Deploy successful!
Preview URL: https://f7cb7293.wireup-landing.pages.dev
Branch Preview URL: https://fix-mcp-send-cheap-daemon-ch.wireup-landing.pages.dev

View logs

@laulpogan laulpogan merged commit a7f50a6 into main Jun 20, 2026
13 checks passed
@laulpogan laulpogan deleted the fix/mcp-send-cheap-daemon-check branch June 20, 2026 20:27
laulpogan added a commit that referenced this pull request Jul 10, 2026
…t self-report

The recurring "display name ≠ actual name" bug: a Claude session's statusline
shows one wire identity while the session operates on wire as another. Confirmed
live — a dot session's statusline read daydream-gorge (its CLAUDE_CODE_SESSION_ID)
while its MCP served merry-spindle, a key matching no live session. Box-wide it's
systemic: 434 running wire procs, 115 serving an identity no live session uses.

Root cause: the long-lived `wire mcp` server resolves + FREEZES its identity at
launch; the statusline re-resolves live each render. When they diverge — the MCP
booted before Claude wrote its session pidfile, so resolve_session_key() missed
and the server minted a throwaway `mcp-proc-*` key, pinned it into WIRE_SESSION_ID,
and served it for life — you get two permanent names. The prior dash detection
(9c3b62a) couldn't see it: a fresh CLI's env is already live, so its env-vs-pidfile
check never inspected the frozen MCP process.

Fixes:
- session.rs: before minting at MCP startup, RETRY the pidfile (walk the ancestor
  chain ONCE, re-read only the cached pids across a short backoff — no per-hop `ps`
  re-fork, avoiding the #353 Windows perf trap). Recovers the live identity instead
  of freezing a throwaway one. Stop pinning the minted key into WIRE_SESSION_ID
  (that slot is the operator-override / live channel; a minted value parked there
  masqueraded as an override and beat the live session on re-resolve — the root of
  the split). WIRE_HOME already pins the process + is inherited by children.
- session.rs: redesign detect_identity_split to compare OPERATIONAL identity (the
  home this process serves) vs LIVE pidfile identity — so the frozen MCP itself
  detects the split, which the old env-vs-pidfile check structurally could not.
  Fixes a latent bug exposed by the redesign: handle_for_key sanitized the key
  before hashing, but homes are created from the raw key hash; sanitize_name
  truncates at 32 chars, so a 36-char session-id uuid resolved to the wrong home
  and the split silently returned None. (Caught by the in-situ gate, not units.)
- mcp.rs: surface `identity_split` in wire_status + wire_whoami (null when healthy)
  so an agent self-detects drift at the session-start health check it already runs,
  instead of waiting for a human to run `wire dash`.
- setup.rs: bake `env:{WIRE_SESSION_ID:"${CLAUDE_CODE_SESSION_ID}"}` into the
  canonical MCP entry `wire setup` writes, so the forward survives `--apply`
  (was a hand-edit the next apply overwrote). Inert on non-Claude hosts (the ${...}
  guard rejects an unexpanded value). Prevention for every new session.

Existing frozen MCPs heal on next /mcp reconnect; the fix + self-report make that
one-command recovery visible. 8 new tests; 643 lib tests green; fmt + clippy clean.
In-situ: reproduced the split with real homes/pidfile — banner fires; silent when
healthy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VxnZEjrcrYR3SAq8Crv8hg
laulpogan added a commit that referenced this pull request Jul 10, 2026
…362)

* fix(identity): self-heal the "two names" split at its source + make it self-report

The recurring "display name ≠ actual name" bug: a Claude session's statusline
shows one wire identity while the session operates on wire as another. Confirmed
live — a dot session's statusline read daydream-gorge (its CLAUDE_CODE_SESSION_ID)
while its MCP served merry-spindle, a key matching no live session. Box-wide it's
systemic: 434 running wire procs, 115 serving an identity no live session uses.

Root cause: the long-lived `wire mcp` server resolves + FREEZES its identity at
launch; the statusline re-resolves live each render. When they diverge — the MCP
booted before Claude wrote its session pidfile, so resolve_session_key() missed
and the server minted a throwaway `mcp-proc-*` key, pinned it into WIRE_SESSION_ID,
and served it for life — you get two permanent names. The prior dash detection
(9c3b62a) couldn't see it: a fresh CLI's env is already live, so its env-vs-pidfile
check never inspected the frozen MCP process.

Fixes:
- session.rs: before minting at MCP startup, RETRY the pidfile (walk the ancestor
  chain ONCE, re-read only the cached pids across a short backoff — no per-hop `ps`
  re-fork, avoiding the #353 Windows perf trap). Recovers the live identity instead
  of freezing a throwaway one. Stop pinning the minted key into WIRE_SESSION_ID
  (that slot is the operator-override / live channel; a minted value parked there
  masqueraded as an override and beat the live session on re-resolve — the root of
  the split). WIRE_HOME already pins the process + is inherited by children.
- session.rs: redesign detect_identity_split to compare OPERATIONAL identity (the
  home this process serves) vs LIVE pidfile identity — so the frozen MCP itself
  detects the split, which the old env-vs-pidfile check structurally could not.
  Fixes a latent bug exposed by the redesign: handle_for_key sanitized the key
  before hashing, but homes are created from the raw key hash; sanitize_name
  truncates at 32 chars, so a 36-char session-id uuid resolved to the wrong home
  and the split silently returned None. (Caught by the in-situ gate, not units.)
- mcp.rs: surface `identity_split` in wire_status + wire_whoami (null when healthy)
  so an agent self-detects drift at the session-start health check it already runs,
  instead of waiting for a human to run `wire dash`.
- setup.rs: bake `env:{WIRE_SESSION_ID:"${CLAUDE_CODE_SESSION_ID}"}` into the
  canonical MCP entry `wire setup` writes, so the forward survives `--apply`
  (was a hand-edit the next apply overwrote). Inert on non-Claude hosts (the ${...}
  guard rejects an unexpanded value). Prevention for every new session.

Existing frozen MCPs heal on next /mcp reconnect; the fix + self-report make that
one-command recovery visible. 8 new tests; 643 lib tests green; fmt + clippy clean.
In-situ: reproduced the split with real homes/pidfile — banner fires; silent when
healthy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VxnZEjrcrYR3SAq8Crv8hg

* fix(identity): fold gate-2 review — cache ancestor walk, suppress fleet-share, document self-report

Gate-2 (fresh-eyes review of the built diff) found one blocker + three majors:

- BLOCKER (perf): detect_identity_split was wired into wire_status/wire_whoami
  (the session-start hot path) but re-walked the parent chain — up to 16 untimed
  `ps` forks — on EVERY call, on a box with 434 wire procs. Cache the ancestor
  chain in a OnceLock (a process's parentage is immutable): the bounded walk
  happens once, every later identity check re-reads only the tiny pidfiles.
- MAJOR (false positive): the RFC-008 §C "deliberate fleet-share" WIRE_HOME pin
  (several sessions intentionally sharing one identity) tripped the detector
  forever. Suppress when session_source is an explicit operator WIRE_HOME pin
  (env:WIRE_HOME / env:WIRE_HOME_FORCE) — wire's own minted/claude-* resolution,
  the real bug case, is never suppressed.
- MAJOR (undocumented): the new identity_split field was present but no agent was
  told to check it. Added it to the wire_status + wire_whoami descriptions and to
  the "ON SESSION START" instructions block (surface it, /mcp reconnect, don't
  send/pair until resolved) — the wiring that makes the self-report actionable.
- MAJOR (test): the regression guard didn't call handle_for_key. Added a hermetic
  test (tempdir WIRE_HOME) that drives handle_for_key directly and proves a raw
  36-char uuid key resolves its home while the sanitized/truncated form misses —
  fails if handle_for_key ever re-adds sanitize_name. Plus a suppression test.

In-situ (real homes + pidfile): fire path fires (operational=merry-spindle ≠
live=verdant-palm); fleet-share WIRE_HOME pin now suppressed (0 banner lines);
healthy silent; cached walk still resolves the live session. 647 lib tests green;
fmt + clippy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VxnZEjrcrYR3SAq8Crv8hg

* docs(changelog): the two-names identity self-heal + self-report (#351)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VxnZEjrcrYR3SAq8Crv8hg

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant