backend/cli/src/auth/index.ts has two problems that combine badly:
set()/remove() are unlocked read-modify-write over auth.json, and Bun.write is not atomic (truncate then write).
all() swallows parse errors and returns {}.
Reproduced:
A) after concurrent sets — providers on disk: [ "provider-a", "provider-b" ]
A) lost: [ "provider-c" ]
B) corrupt auth.json reads as {}
B) after one Auth.set on the corrupt file: { "openai-codex": {...} } <- anthropic key GONE
Failure scenarios:
- a Codex token refresh persists rotated tokens at the same moment the user logs into another provider; one credential silently vanishes
- a torn read during a concurrent write, or a disk-full truncation, makes
all() return {}; the next routine token refresh then rewrites auth.json containing only itself, permanently destroying every other provider credential with no warning
This is the persistence layer underneath the codex rotation fix from #20, so the cross-process race that fix targets can still destroy state one level down. The same unlocked pattern exists in backend/cli/src/mcp/auth.ts (mcp-auth.json).
Fixes: write via temp file + rename, take a file lock (the repo already has a Lock util used by BunProc.install), and treat an unparseable auth.json as fatal for writes (back it up and refuse) instead of as {}.
backend/cli/src/auth/index.tshas two problems that combine badly:set()/remove()are unlocked read-modify-write overauth.json, andBun.writeis not atomic (truncate then write).all()swallows parse errors and returns{}.Reproduced:
Failure scenarios:
all()return{}; the next routine token refresh then rewritesauth.jsoncontaining only itself, permanently destroying every other provider credential with no warningThis is the persistence layer underneath the codex rotation fix from #20, so the cross-process race that fix targets can still destroy state one level down. The same unlocked pattern exists in
backend/cli/src/mcp/auth.ts(mcp-auth.json).Fixes: write via temp file + rename, take a file lock (the repo already has a
Lockutil used byBunProc.install), and treat an unparseable auth.json as fatal for writes (back it up and refuse) instead of as{}.