Skip to content

Commit 0dcc526

Browse files
committed
docs(codex): note refresh-lock PID-namespace and lock-path limits
1 parent acfc0de commit 0dcc526

1 file changed

Lines changed: 16 additions & 1 deletion

File tree

‎src/auth/codex/refresh-lock.ts‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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.
1425
const 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.
7689
function 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
*/
185200
export async function withCodexRefreshLock<T>(
186201
lockPath: string,

0 commit comments

Comments
 (0)