Problem
Users report that providers keep disappearing from ~/.local/share/opencode/auth.json. Investigation reveals race conditions and error handling flaws in the authentication storage system.
Root Cause Analysis
1. Non-Atomic Read-Modify-Write Pattern
Auth.set() in packages/opencode/src/auth/index.ts:
exportasyncfunctionset(key: string,info: Info){constfile=Bun.file(filepath)constdata=awaitall()// READawaitBun.write(file,JSON.stringify({ ...data,[key]: info},null,2))// WRITEawaitfs.chmod(file.name!,0o600)}Problem: Between read and write, another process can write, causing data loss.
2. Silent Failure Returns Empty Object
Auth.all() silently returns {} on any read failure:
constdata=awaitfile.json().catch(()=>({})asRecord<string,unknown>)Problem: If file is corrupted/unreadable, next Auth.set() writes empty object + new provider, erasing all existing providers.
3. No File Locking
A Lock utility exists at util/lock.ts but is not used by the auth module. Multiple processes compete for the same file:
- CLI TUI
- Dev server (
bun dev -- serve) - Web app (via SDK)
- Desktop app
- Multiple terminal windows
4. Race Condition Timeline
| Time | Process A | Process B | File State |
|---|
| T1 | Reads: { p1, p2 } | - | { p1, p2 } |
| T2 | Adding p3... | Reads: { p1, p2 } | { p1, p2 } |
| T3 | Writes: { p1, p2, p3 } | - | { p1, p2, p3 } |
| T4 | Done | Writes: { p1, p2 } | { p1, p2 } ❌ p3 lost |
Key Files
| File | Purpose |
|---|
packages/opencode/src/auth/index.ts | Core auth.json read/write operations |
packages/opencode/src/provider/auth.ts | Provider auth flow (OAuth/API key) |
packages/opencode/src/util/lock.ts | Lock utility (NOT used by auth) |
Suggested Fix
Option 1: Use Existing Lock Utility
import{Lock}from"../util/lock"exportasyncfunctionset(key: string,info: Info){constrelease=awaitLock.write("auth")try{constfile=Bun.file(filepath)constdata=awaitall()awaitBun.write(file,JSON.stringify({ ...data,[key]: info},null,2))awaitfs.chmod(file.name!,0o600)}finally{release()}}Option 2: Atomic Write with Rename
exportasyncfunctionset(key: string,info: Info){constfile=Bun.file(filepath)consttempFile=filepath+".tmp."+crypto.randomUUID()constdata=awaitall()awaitBun.write(tempFile,JSON.stringify({ ...data,[key]: info},null,2))awaitfs.chmod(tempFile,0o600)awaitfs.rename(tempFile,filepath)// Atomic on most filesystems}Option 3: Better Error Handling
exportasyncfunctionall(): Promise<Record<string,Info>>{constfile=Bun.file(filepath)if(!awaitfile.exists())return{}try{constdata=awaitfile.json()// ... validation}catch(e){log.error("Failed to read auth.json, preserving existing file",{error: e})throwe// Don't silently return empty object!}}Priority
High - Data loss affecting user credentials
Reproduction
- Open two terminal windows
- Run
bun dev in both (or bun dev + bun dev -- serve) - Add a provider in one terminal
- Add a different provider in the other terminal quickly
- Check auth.json - one provider may be missing
Problem
Users report that providers keep disappearing from
~/.local/share/opencode/auth.json. Investigation reveals race conditions and error handling flaws in the authentication storage system.Root Cause Analysis
1. Non-Atomic Read-Modify-Write Pattern
Auth.set()inpackages/opencode/src/auth/index.ts:Problem: Between read and write, another process can write, causing data loss.
2. Silent Failure Returns Empty Object
Auth.all()silently returns{}on any read failure:Problem: If file is corrupted/unreadable, next
Auth.set()writes empty object + new provider, erasing all existing providers.3. No File Locking
A Lock utility exists at
util/lock.tsbut is not used by the auth module. Multiple processes compete for the same file:bun dev -- serve)4. Race Condition Timeline
{ p1, p2 }{ p1, p2 }{ p1, p2 }{ p1, p2 }{ p1, p2, p3 }{ p1, p2, p3 }{ p1, p2 }{ p1, p2 }❌ p3 lostKey Files
packages/opencode/src/auth/index.tspackages/opencode/src/provider/auth.tspackages/opencode/src/util/lock.tsSuggested Fix
Option 1: Use Existing Lock Utility
Option 2: Atomic Write with Rename
Option 3: Better Error Handling
Priority
High - Data loss affecting user credentials
Reproduction
bun devin both (orbun dev+bun dev -- serve)