fix(config): bind cross-process file locks to their owning host - #3609
fix(config): bind cross-process file locks to their owning host#3609twoimo wants to merge 1 commit into
Conversation
Owner liveness is probed against the local process table, but a state directory on a shared volume (NFS home directories are supported) is contended by several hosts. A remote owner's pid is almost never a live local pid, so a foreign host's freshly-taken lock read as `dead` and was reclaimed immediately with no stale grace period, letting two hosts run the same critical section concurrently. Stamp the owning host into the lock record. A foreign-host owner is now reclaimable only after the `staleMs` elapsed-time heuristic - the same grace a local owner of indeterminate liveness receives - and host identity is part of owner equality for the guarded lock-dir removal, so one host cannot delete another's lock on a pid/timestamp coincidence. Records without a host id keep the previous local-pid semantics.
Yeachan-Heo
left a comment
There was a problem hiding this comment.
REQUEST_CHANGES — exact-head lock authority review
Head: 80ccfbd703d2e12191439e437eca7687b2e85b89
Base/current intake dev: 9547ca6aea41efcfa653156c19f6a91d52ec849c
Verdict: P0 = 0, P1 = 3
- Foreign locks are reclaimed solely from elapsed contender wall time. A live remote critical section longer than
staleMs, or ordinary clock skew, permits concurrent ownership without remote liveness/fencing proof. hostname/unknownis not collision-resistant node/boot identity, and hostless legacy records are treated as local. Cloned/container hosts and rolling upgrades can therefore reclaim a live foreign owner via local PID probing.file-lock-gc.tsbypasses the foreign-host decision and can delete a live remote lock immediately after localESRCH.
This also directly overlaps active #3596 ownership of file-lock.ts; no owner disposition clears that contract. Use one unified fail-closed rule across acquisition and GC: retain foreign, host-colliding, legacy, or unverifiable records unless a distributed liveness/fencing authority proves retirement. Cover same-hostname clones, boot identity, rolling upgrade, skew, long-lived remote holders, and GC pruning.
Exact GitHub Actions runs 30580152139 and 30580152094 are green, but CI does not override these authority blockers.
—
[repo owner's gaebal-gajae (clawdbot) 🦞]
|
Closed unmerged at exact reviewed head — [repo owner's gaebal-gajae (clawdbot) 🦞] |
Hello, and thank you for maintaining this project.
While assessing the Telegram topic-authority slice I traced its shared-volume
concern down to a defect that is entirely independent of it, in
packages/coding-agent/src/config/file-lock.ts. I have separated that fix hereso it can be judged on its own.
The defect
withFileLockdecides whether an existing lock is stale by probing the owner'sliveness against the local process table:
That is sound for a lock this host wrote. But the state directory may live on a
shared volume —
packages/natives/CHANGELOG.mdexplicitly supports "a sessionstore on an NFS home directory" — in which case several hosts contend for the
same lock path. A remote owner's pid is almost never a live local pid, so
ownerLivenessanswersdeadand the branch reclaims the lock immediately,with no
staleMsgrace period at all.The consequence is that two hosts can hold the same lock and run the same
critical section concurrently.
withFileLockis the serialization primitivebehind settings writes, GC stores, and the notification daemon's state, so this
is a silent mutual-exclusion failure rather than a cosmetic one.
A second, narrower hole:
removeFileLockDirForGccompares owner equality on{pid, start_time?, timestamp}only. Two hosts can coincide on pid andtimestamp, in which case one host's guarded removal can delete the other's lock.
The change
host_id).deadfast path. It isreclaimable only after the
staleMselapsed-time heuristic — precisely thegrace period a local owner of indeterminate liveness already receives, so no
new liveness policy is introduced.
host_idand keep the previouslocal-pid semantics exactly, so existing on-disk locks are unaffected.
I deliberately did not add any new native primitive, filesystem capability,
or loader requirement. This is a behavioural fix to existing TypeScript, and it
does not touch
crates/pi-natives/src/path_identity.rs,packages/natives/native/index.d.ts, or the daemon generation manifest — Iunderstand #3596 owns those.
Verification
New suite
packages/coding-agent/test/file-lock-cross-host.test.ts(6 tests). Iconfirmed it is not a tautology by reverting the source and re-running: three
tests fail on current
dev, including the headline one.Every existing suite that exercises
withFileLock— including the #606 TOCTOUguard — stays green:
tsc -p packages/coding-agent/tsconfig.json --noEmitandbiome checkare bothclean. Rebased onto
devat9547ca6ae.A note on coverage
The reclaim path is exercised through the public
withFileLockAPI using asynthetic foreign-host record, not by a genuine two-host NFS test — I have no
shared-volume host pair available. I want to be straightforward that the
multi-host scenario itself is reasoned from the code path rather than reproduced
on real hardware, even though the guard it installs is directly tested.
Thank you for your time reviewing this.