Repository navigation
Description
Summary
get_serve_info() decides whether the serve lock (.iroh-serve.lock) is stale by checking only that the recorded pid exists — is_process_alive(pid) is kill(pid, 0) == 0 (pkgs/id/src/commands/serve.rs:200-210). It never checks that the process holding that pid is the serve that wrote the lock. When a serve dies without removing its lock (crash, kill -9, power loss) and the OS later hands that pid to an unrelated long-lived process, the lock passes the staleness check forever: get_serve_info returns the dead serve's ServeInfo (serve.rs:171-185), and every command that consults the lock first — get (commands/get.rs:101), list (commands/list.rs:72), put, peers, the REPL, the meta client (meta_client.rs:103) — tries to connect to the recorded addresses of a serve that no longer exists instead of falling back to opening the local store directly. The failure presents as a connection error against a phantom endpoint; nothing points at the lock file.
This is the reuse sibling of #2142, which covers sentinel pids read from disk (0, u32::MAX) answering "alive" for no process at all. #2142's fix (refuse non-positive / out-of-range pids) does nothing here: a reused pid is an ordinary in-range pid held by a real process — just the wrong one.
Evidence (verified this run, main a9df3018)
The gate in get_serve_info is straight-line: read the lock JSON → parse ServeInfo (serve.rs:130-141) → is_process_alive(info.pid) → Some(info), else delete the lock and return None. The only staleness test is pid existence.
Laptop probe (~/git/fleet/probe-2026-10-31-laneB-pidreuse/; is_process_alive copied verbatim from serve.rs:200-210 with libc::kill spelled as the FFI call it wraps; serve.rs md5 9a3eba3e2c09114e28d6932f8a102fea identical in the sandbox and laptop clones):
pid 2086858: comm=sleep state=S
gate accepts lock while pid is held by sleep: true
gate accepts same lock after sleep exits: false
RESULT: the lock staleness check tests pid existence, not serve identity
The probe spawns an unrelated sleep 120 (verified live and running via /proc/<pid>/comm = sleep, state S — not a zombie, not an id serve), writes a lock naming that pid with a dead endpoint's addresses, and the gate accepts it; the moment the sleeper exits, the same lock is refused. So the check cannot distinguish "serve alive" from "any process alive with that pid".
Why it matters
Serve discovery is the front door for most CLI commands in a served directory. A crashed serve plus ordinary pid reuse (routine on long-uptime machines and in containers with small pid namespaces, where wraparound is fast) turns a recoverable state into persistent, misleading connection failures in that directory until a human deletes .iroh-serve.lock by hand — the same wedge class as #2142, reached by a mundane path rather than a corrupt file.
Acceptance criteria
- The staleness check verifies the pid holder is the serve that wrote the lock (e.g. the lock records the serve's start time / exe identity and
get_serve_infocompares it, or an equivalent identity check), so a lock whose pid was reused by an unrelated process is treated as stale: removed, andNonereturned. - A lock written by a live serve behaves exactly as before (discovery still succeeds against the running serve).
- A lock with an ordinary dead pid behaves exactly as before (removed,
None). - Regression tests cover: live serve pid accepted; dead pid refused; live unrelated pid refused.
- The fix states how it behaves on non-Unix platforms, where
is_process_alivecurrently always answerstrue.
Dedup: #2142 (open) covers sentinel pids 0/u32::MAX only; its acceptance criteria do not include pid-reuse identity. Searches for serve lock pid reuse, is_process_alive, and stale lock returned no other covering issue.
Activity
dezren39 commentedon Oct 31, 2026
🤖 Agent note · filed
117 remaining items
dezren39 commentedon Nov 4, 2026
agent-note:v1
event: parked
task: 5649 (small_fix, repo-health-code-pilot; repo-worker-code; OpenChamber scheduled lane 02cafe76-eafd-4d0b-aaa6-2b7a737d68dc)
session: ses_e5e5b5b8e50ffeShFhVywaC0NL
state: parked for 30 minutes. Fresh checks confirm #3040 OPEN, assigned to dezren39, and matching PR #4090 still OPEN draft and authored by dezren39. PR checks report SUCCESS, but active ownership remains. This lane did not inspect PR contents or the existing worktree and did not create competing work.
labels: queue/parked and status/parked set; queue/claimed and status/in-progress removed; work/assigned retained.
resume: revalidate issue #3040 and PR #4090 state/owner/head; proceed only after that PR closes/merges or ownership changes.
dezren39 commentedon Nov 4, 2026
🤖 Agent note · handed off