Summary
The per-home session lock (bin/fm-lock.sh) identifies the lock-holding process by walking ps ancestry and matching a fixed harness command-name regex. Because the test is "does a process in my ancestry (or the recorded pid) look like a harness," and not "is this the specific process that acquired the fleet lock," any process descended from — or sharing a command name with — one of the known harnesses is indistinguishable from the true fleet session. In a multi-harness process tree (e.g. a codex- or opencode-harnessed crewmate running alongside the main claude session), holder identification can latch onto the wrong harness, and the liveness check that gates the read-only-second-session safety cannot reliably tell the real owner from an unrelated harness-descended process.
Root cause
bin/fm-lock.sh recognizes harnesses purely by command name, via one regex:
# bin/fm-lock.sh:17-18
# Known harness command names; extend when a new adapter is verified.
HARNESS_RE='claude|codex|opencode|grok|^pi$'
Holder discovery walks up to 8 ancestors from the current shell and returns the first pid whose command basename (or, for a bare node/python interpreter, whose args) matches that regex:
# bin/fm-lock.sh:20-36
harness_pid() {
local pid=$$ comm args
for _ in 1 2 3 4 5 6 7 8; do
comm=$(ps -o comm= -p "$pid" 2>/dev/null) || return 1
args=$(ps -o args= -p "$pid" 2>/dev/null)
if printf '%s' "$(basename "$comm")" | grep -qE "$HARNESS_RE"; then
echo "$pid"; return 0
fi
# Bare interpreter (e.g. node): match the harness name in its script path.
case "$comm" in
*node*|*python*) printf '%s' "$args" | grep -qE "$HARNESS_RE" && { echo "$pid"; return 0; } ;;
esac
pid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ')
[ -n "$pid" ] && [ "$pid" -gt 1 ] || return 1
done
return 1
}
Liveness of the recorded holder is decided by the same "looks like a harness" test — a pid that is alive and whose comm/args match the regex:
# bin/fm-lock.sh:38-43
holder_alive() { # true if $1 is a live process that looks like a harness
local pid=$1 comm
kill -0 "$pid" 2>/dev/null || return 1
comm=$(ps -o comm= -p "$pid" 2>/dev/null) || return 1
printf '%s' "$(basename "$comm") $(ps -o args= -p "$pid" 2>/dev/null)" | grep -qE "$HARNESS_RE"
}
Acquisition then refuses only when the recorded pid differs from the caller's discovered harness_pid and that recorded pid is holder_alive:
# bin/fm-lock.sh:52-61
me=$(harness_pid) || { echo "error: cannot locate harness process in ancestry" >&2; exit 1; }
if [ -f "$LOCK" ]; then
old=$(cat "$LOCK")
if [ "$old" != "$me" ] && holder_alive "$old"; then
echo "error: another live firstmate session holds the lock (pid $old); operate read-only until resolved" >&2
exit 1
fi
fi
echo "$me" > "$LOCK"
The heuristic never stores or checks any per-session identity token. Both the discovery walk and the liveness test answer a category question ("is this a harness?"), not an identity question ("is this the fleet session that owns this home?"). Two independent failure surfaces follow:
-
Ambiguous discovery. harness_pid returns the first harness-named ancestor. In a nested or mixed process tree — a tool subshell whose ancestry passes through a codex- or opencode-harnessed crewmate before (or instead of) reaching the true fleet-session harness — the discovered me can be a harness that is not the fleet session. The lock file is then written with, or compared against, the wrong pid.
-
Non-identifying liveness. holder_alive(old) returns true for any live process whose comm/args match HARNESS_RE. It cannot confirm that old is still the same session that wrote the lock. A recorded pid that has died and been reused by the OS for any harness-named process would read as "held by a live harness," and, conversely, the real owner is only ever confirmed as "some harness," never as "this home's session."
Concrete example (tonight's real process mix — realistic failure surface, not an observed incident)
To be clear up front: no lock theft or mis-acquisition actually occurred tonight — the lock correctly holds the fleet session. This is the real process mix that shows why the identification cannot be relied on, not a report of a failure event.
Tonight (2026-07-03) this firstmate home's lock file state/.lock records a single pid:
$ cat state/.lock
87247
$ ps -o pid=,comm=,args= -p 87247
87247 claude claude --model claude-fable-5 --dangerously-skip-permissions
At the same moment, the process table held several other processes that all match HARNESS_RE:
$ ps -eo pid,comm,args | grep -E 'claude|codex|opencode|grok'
2992 claude claude
69043 claude claude --resume 6ded525a-...
79122 claude claude
26825 opencode opencode
... (Codex.app app-server subtree, comm/args containing "codex")
In addition, this home dispatched a codex-harnessed crewmate earlier tonight (task herdr-claude-state-m7, since merged), i.e. a live process subtree whose command name matches codex running concurrently with the main claude fleet session.
So processes descended from / named after both claude and codex/opencode were live simultaneously, and the ancestry regex matches every one of them. holder_alive(87247) returns true — but it would also return true for pid 2992, 69043, 79122, or 26825, none of which is the fleet session. Nothing in fm-lock.sh distinguishes the true owner (87247) from these unrelated harness-descended processes beyond "its command name matches the regex," which they all do. If a second session's harness_pid walk had latched onto a different harness ancestor, or if pid 87247 had exited and its number been reused by any harness-named process, the holder-identification and liveness logic would give a confidently wrong answer.
Impact
The read-only-second-session guard — a core safety property (a second live session must detect the first and operate read-only so two sessions never mutate one home's fleet state) — rests entirely on this identification being trustworthy. Because the check verifies harness category rather than session identity:
- A second session can mis-identify who holds the lock in a multi-harness tree, either false-refusing (treating an unrelated live harness as the owner) or, in the pid-reuse / wrong-ancestor case, failing to recognize a genuinely live owner.
- The guarantee that "at most one session mutates this home" degrades from a reliable invariant to a best-effort heuristic whose correctness depends on the ambient process mix.
This is a correctness/safety issue in the lock's identity model, independent of any single reproduction, and it grows more likely as multi-harness fleets (codex/opencode/grok crewmates alongside a claude primary) become common.
Suggested direction
Identify the holder by a recorded session-identity token rather than by harness-command-name ancestry:
- Have the fleet session mint a token at session start (e.g. the harness pid plus its process start time, or a random session UUID written once) and store it in the lock file. Compare identity against that token on acquire and on
status, so "same session" is an exact match, not a category match.
- Pairing pid with start-time (available from
ps -o lstart=/ps -o etimes= or /proc/<pid>/stat starttime) closes the pid-reuse hole: a reused pid will have a different start time and fail the identity check.
- Keep the harness regex, if useful at all, only as a soft sanity hint — not as the thing that decides ownership or liveness.
This preserves the current UX (one lock file per home, status still prints holder + liveness) while making holder identification an identity question the multi-harness case can answer correctly.
Line references are to bin/fm-lock.sh at commit 66f12e9 (main as of filing): https://github.com/kunchenguid/firstmate/blob/66f12e9/bin/fm-lock.sh
Summary
The per-home session lock (
bin/fm-lock.sh) identifies the lock-holding process by walkingpsancestry and matching a fixed harness command-name regex. Because the test is "does a process in my ancestry (or the recorded pid) look like a harness," and not "is this the specific process that acquired the fleet lock," any process descended from — or sharing a command name with — one of the known harnesses is indistinguishable from the true fleet session. In a multi-harness process tree (e.g. a codex- or opencode-harnessed crewmate running alongside the main claude session), holder identification can latch onto the wrong harness, and the liveness check that gates the read-only-second-session safety cannot reliably tell the real owner from an unrelated harness-descended process.Root cause
bin/fm-lock.shrecognizes harnesses purely by command name, via one regex:Holder discovery walks up to 8 ancestors from the current shell and returns the first pid whose command basename (or, for a bare
node/pythoninterpreter, whose args) matches that regex:Liveness of the recorded holder is decided by the same "looks like a harness" test — a pid that is alive and whose comm/args match the regex:
Acquisition then refuses only when the recorded pid differs from the caller's discovered
harness_pidand that recorded pid isholder_alive:The heuristic never stores or checks any per-session identity token. Both the discovery walk and the liveness test answer a category question ("is this a harness?"), not an identity question ("is this the fleet session that owns this home?"). Two independent failure surfaces follow:
Ambiguous discovery.
harness_pidreturns the first harness-named ancestor. In a nested or mixed process tree — a tool subshell whose ancestry passes through a codex- or opencode-harnessed crewmate before (or instead of) reaching the true fleet-session harness — the discoveredmecan be a harness that is not the fleet session. The lock file is then written with, or compared against, the wrong pid.Non-identifying liveness.
holder_alive(old)returns true for any live process whose comm/args matchHARNESS_RE. It cannot confirm thatoldis still the same session that wrote the lock. A recorded pid that has died and been reused by the OS for any harness-named process would read as "held by a live harness," and, conversely, the real owner is only ever confirmed as "some harness," never as "this home's session."Concrete example (tonight's real process mix — realistic failure surface, not an observed incident)
To be clear up front: no lock theft or mis-acquisition actually occurred tonight — the lock correctly holds the fleet session. This is the real process mix that shows why the identification cannot be relied on, not a report of a failure event.
Tonight (2026-07-03) this firstmate home's lock file
state/.lockrecords a single pid:At the same moment, the process table held several other processes that all match
HARNESS_RE:In addition, this home dispatched a codex-harnessed crewmate earlier tonight (task
herdr-claude-state-m7, since merged), i.e. a live process subtree whose command name matchescodexrunning concurrently with the main claude fleet session.So processes descended from / named after both
claudeandcodex/opencodewere live simultaneously, and the ancestry regex matches every one of them.holder_alive(87247)returns true — but it would also return true for pid 2992, 69043, 79122, or 26825, none of which is the fleet session. Nothing infm-lock.shdistinguishes the true owner (87247) from these unrelated harness-descended processes beyond "its command name matches the regex," which they all do. If a second session'sharness_pidwalk had latched onto a different harness ancestor, or if pid 87247 had exited and its number been reused by any harness-named process, the holder-identification and liveness logic would give a confidently wrong answer.Impact
The read-only-second-session guard — a core safety property (a second live session must detect the first and operate read-only so two sessions never mutate one home's fleet state) — rests entirely on this identification being trustworthy. Because the check verifies harness category rather than session identity:
This is a correctness/safety issue in the lock's identity model, independent of any single reproduction, and it grows more likely as multi-harness fleets (codex/opencode/grok crewmates alongside a claude primary) become common.
Suggested direction
Identify the holder by a recorded session-identity token rather than by harness-command-name ancestry:
status, so "same session" is an exact match, not a category match.ps -o lstart=/ps -o etimes=or/proc/<pid>/statstarttime) closes the pid-reuse hole: a reused pid will have a different start time and fail the identity check.This preserves the current UX (one lock file per home,
statusstill prints holder + liveness) while making holder identification an identity question the multi-harness case can answer correctly.Line references are to
bin/fm-lock.shat commit66f12e9(mainas of filing): https://github.com/kunchenguid/firstmate/blob/66f12e9/bin/fm-lock.sh