Skip to content

id(serve): serve-lock staleness check tests pid existence, not serve identity — a reused pid wedges serve discovery on a dead endpoint #3040

Open

Description

@dezren39

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_info compares it, or an equivalent identity check), so a lock whose pid was reused by an unrelated process is treated as stale: removed, and None returned.
  • 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_alive currently always answers true.

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

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/triageQueue packet kind: verify, reproduce, and route this item
    source/creatorFiled by the new-issue-creator job
    priority/lowNice to have; do when convenient
    queue/queuedHas a queued task in the Supabase agent queue
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 31, 2026
  2. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  3. added
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  4. 117 remaining items

  5. added
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 4, 2026
  6. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    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.

  7. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 4, 2026
  8. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  9. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)effort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientqueue/claimedQueue worker lifecycle statesource/creatorFiled by the new-issue-creator jobstatus/in-progressQueue worker lifecycle statetask/small-fixQueue packet kind: bounded fix with reproduction and targeted testswork/assignedQueue worker lifecycle state

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions