@@ -11,6 +11,17 @@ import { dirname } from "node:path";
1111// for one credential store. At most one refresh grant is ever in flight, so
1212// a second refresher observes the persisted result instead of racing the
1313// authorization server's refresh-token rotation and revoking its sibling.
14+ //
15+ // Limits (accepted; follow-ups, not fixes here). The PID-liveness takeover
16+ // assumes contender and holder share one PID namespace on one host: across
17+ // namespaces (containers) or machines (network credential store) the PID
18+ // check is meaningless — ESRCH steals a live holder's lock (overlapping
19+ // grants) while a recycled PID reads alive and stalls recovery. Likewise,
20+ // serialization needs one canonical lock path per store: contenders that
21+ // spell the same store via two paths (symlinked TMPDIR, uncanonicalized
22+ // home) contend on two files and never meet. Same-host headless runs share
23+ // namespace and path, so the lock holds; canonicalizing the lock path
24+ // (realpath of the store dir) is a follow-up, not this change.
1425const tails = new Map < string , Promise < void > > ( ) ;
1526
1627// Default stale horizon for legacy lock files that carry no holder PID
@@ -72,7 +83,9 @@ function holderPid(content: string): number | null {
7283// means it exists but belongs to another user (alive); ESRCH/EINVAL mean no
7384// such process (dead). Any other failure is treated as alive — never steal
7485// a live holder's lock on a confused signal check; the waiter times out with
75- // a recovery hint instead.
86+ // a recovery hint instead. Meaningful only when contender and holder share
87+ // a PID namespace (see the module header): across namespaces the answer is
88+ // about the wrong process table.
7689function isPidAlive ( pid : number ) : boolean {
7790 try {
7891 process . kill ( pid , 0 ) ;
@@ -181,6 +194,8 @@ async function waitForTail(
181194 * lock file. The lock file is removed afterwards by the holder that created
182195 * it. One acquisition deadline covers both the queue wait and the file
183196 * contention, so the call either holds the lock or throws within ~timeoutMs.
197+ * Callers must pass one canonical path per credential store: two spellings
198+ * of the same store contend on two files (see the module header).
184199 */
185200export async function withCodexRefreshLock < T > (
186201 lockPath : string ,
0 commit comments