anomalyco / anomalyco/opencode
[Bug] auth.json rewritten wholesale on Auth mutation — schema-incompatible credentials silently deleted; background metadata refresh triggers data loss
@jlongster is already working on this.
Since Aug 13, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- Avg merge
- 7h 2m
- Merged PRs (30d)
- 384
Description
Description
Summary
Every Auth.set() / Auth.remove() rewrites the ENTIRE ~/.local/share/opencode/auth.json.
Auth.all() silently discards every entry that fails schema decoding, so any single
mutation permanently deletes credentials whose JSON shape the running build doesn't
recognize. The Desktop app additionally performs scheduled background writes to the
same file (provider-metadata refresh — e.g. DigitalOcean routers, refreshed ~every 5
min, which calls auth.set and bumps metadata.routers_fetched_at), so schema-incompatible
credentials disappear later "by themselves" — no user action involved.
Environment
- Windows 10
- OpenCode CLI 1.18.18 (npm) AND Desktop app (@opencode-aidesktop) sharing one auth.json
- First mass-loss observed right after upgrading into the v1.18.16 era
Verified mechanism (v1.18.15 source, confirmed present in v1.18.18)
packages/opencode/src/auth/index.ts:
Auth.all(): Record.filterMap(data, v => Result.fromOption(decode(v), () => undefined))
// ^ entries failing schema decode are silently dropped
Auth.set(key, info): writeJson(file, { ...all(), [norm]: info }) // FULL rewrite
Auth.remove(key): writeJson(file, all()) // FULL rewrite
No inter-process locking around the read→modify→write sequence.
Live reproduction (verified on 1.18.18, under 2 minutes, no real tokens)
- auth.json contains only
digitalocean: { "type": "api", ... }(schema-valid). - Inject one entry that fails the legacy OAuth schema (missing required fields):
"decoy-bad-oauth": { "type": "oauth", "access": "fake" } - Run
opencode auth logout digitalocean(target: digitalocean ONLY) - auth.json is now EMPTY — the decoy was deleted too, although
removenever
addressed it. No warning, no log entry. (Real credential restored from backup.)
This reproduces the production wipe signature exactly: one mutation erases every
entry the executing build cannot decode.
Writer analysis (production trigger)
opencode auth listleaves auth.json untouched (identical sha256 + mtime before/after).- With only the Desktop app running and zero user auth actions, auth.json was
rewritten multiple times in one day;digitalocean.metadata.routers_fetched_at
advanced (1786420850546 -> 1786644641977) — consistent with the DigitalOcean
routers refresh path callingauth.seton a schedule. - Each such rewrite is a wipe event for every entry the running build's schema
rejects (e.g. entries written by a different CLI/Desktop build).
Observed mass wipe
After upgrading to v1.18.16-era builds, auth.json lost ALL OAuth credentials
(openai OAuth, github-copilot OAuth, nvidia, fireworks). Only the type: "api"
static-key digitalocean entry survived. auth.json.bak (made post-incident) is
byte-identical to the wiped file.
What is NOT the cause
The V2 migration 20260805200742_import_legacy_credentials.ts explicitly supports
OAuth (converts to Credential.OAuth); #41607 only swapped Bun.file → node:fs
reads. The importer preserves OAuth. The loss happens in the legacy Auth
filter+rewrite path / V1↔V2 sync.
Also: v1.18.15...v1.18.16 contains 12 commits, none auth-related — the wipe
starting "after the update" is shape-drift-triggered, not version-triggered.
Related: #40920 (V1↔V2 credential consolidation drops creds stored in only one
store), #41820 (Copilot OAuth present but provider unregistered — different bug,
kept as context), #41173, #41607.
Suggested fixes (any mitigates; first is minimal)
- Preserve undecodable entries verbatim on save: merge the mutation into the RAW
map, not the filtered map. Never silently delete credentials. - Serialize read→modify→write access to auth.json with an inter-process lock or a
single-writer model. (Add atomic temp+rename only as crash-safety against torn
writes — it does not fix cross-process lost updates.) - Stop scheduled Auth.set() for volatile metadata (routers, fetched_at) — store it
outside the credential file.
Workaround for affected users
Move providers to static API keys via env vars or provider.<id>.options.apiKey
in opencode.json — bypasses auth.json entirely.
Plugins
No response
OpenCode version
1.18.18
Steps to reproduce
No response
Screenshot and/or share link
No response
Operating System
Windows 10
Terminal
PowerShell
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.