Summary
When a firstmate session acquires the session lock through Claude Code's reparented per-user worker pool, bin/fm-lock.sh publishes the shared pool daemon's pid rather than the acquiring session's own pid. Because ownership is decided by ancestry membership, any other Claude session served by that same per-user pool then passes the ownership test for that home. That breaks the exclusivity the lock exists to provide.
This is pre-existing behaviour on unmodified main. Filing it separately because it was surfaced during review of #2839, which is deliberately scoped to a different mechanism and is inert on this path.
Reproduction path
state/.lock names a prior session that is now dead.
- A new session starts with
CLAUDE_PID=700, but its tool call is served from the pool, so its ancestry reaches only 320 -> 310 -> 300 -> 1 and never reaches 700.
me=$(fm_harness_ancestry_pid) therefore resolves to the shared pool daemon pid 300.
- The stale lock is replaced and publication records
300.
- A different Claude session served by that same per-user pool also has
300 in its ancestry, so fm_session_lock_owned_by_self returns true for it against the same home.
Evidence that this is pre-existing
On unmodified main:
bin/fm-lock.sh line 36 already derives the published identity purely from ancestry:
me=$(fm_harness_ancestry_pid) || { echo "error: cannot locate harness process in ancestry" >&2; exit 1; }
Under a pool reparented to init, that is the daemon pid, so main publishes 300 in this scenario.
bin/fm-session-lock-lib.sh fm_session_lock_owned_by_self already decides ownership by ancestry membership of the recorded pid, so a sibling session under the same pool already satisfies it.
Both halves of the defect are therefore present without any change applied.
Impact
Two live sessions sharing one per-user worker pool can each believe they own the same home's session lock. Lock-gated operations - spawn, steer, merge, wake drain, supervision repair - are then no longer serialized between them.
Suggested direction
The identity that gets written needs resolving at the shared acquisition boundary, after the existing lock has been proven free or stale, rather than being taken from an ancestry walk that cannot see past the pool. Any fix should preserve the existing rule that a published session id alone must never let a caller claim a lock a different live session currently holds.
Note that #2437 already proposes a durable session-identity binding recorded at acquisition, which addresses this class of problem directly; this issue may be best resolved as part of whichever ownership mechanism the maintainers select.
Summary
When a firstmate session acquires the session lock through Claude Code's reparented per-user worker pool,
bin/fm-lock.shpublishes the shared pool daemon's pid rather than the acquiring session's own pid. Because ownership is decided by ancestry membership, any other Claude session served by that same per-user pool then passes the ownership test for that home. That breaks the exclusivity the lock exists to provide.This is pre-existing behaviour on unmodified
main. Filing it separately because it was surfaced during review of #2839, which is deliberately scoped to a different mechanism and is inert on this path.Reproduction path
state/.locknames a prior session that is now dead.CLAUDE_PID=700, but its tool call is served from the pool, so its ancestry reaches only320 -> 310 -> 300 -> 1and never reaches 700.me=$(fm_harness_ancestry_pid)therefore resolves to the shared pool daemon pid300.300.300in its ancestry, sofm_session_lock_owned_by_selfreturns true for it against the same home.Evidence that this is pre-existing
On unmodified
main:bin/fm-lock.shline 36 already derives the published identity purely from ancestry:mainpublishes300in this scenario.bin/fm-session-lock-lib.shfm_session_lock_owned_by_selfalready decides ownership by ancestry membership of the recorded pid, so a sibling session under the same pool already satisfies it.Both halves of the defect are therefore present without any change applied.
Impact
Two live sessions sharing one per-user worker pool can each believe they own the same home's session lock. Lock-gated operations - spawn, steer, merge, wake drain, supervision repair - are then no longer serialized between them.
Suggested direction
The identity that gets written needs resolving at the shared acquisition boundary, after the existing lock has been proven free or stale, rather than being taken from an ancestry walk that cannot see past the pool. Any fix should preserve the existing rule that a published session id alone must never let a caller claim a lock a different live session currently holds.
Note that #2437 already proposes a durable session-identity binding recorded at acquisition, which addresses this class of problem directly; this issue may be best resolved as part of whichever ownership mechanism the maintainers select.