fix(opencode): write auth.json atomically under a lock - #46131
Open
iceteaSA wants to merge 2 commits into
Open
Conversation
Auth mutations previously used the OPENCODE_AUTH_CONTENT read snapshot as the source for file updates, erasing credentials written after the snapshot was created. Read the auth file directly for set and remove while retaining the environment override for read-time all and get behavior.
Auth mutations previously read and rewrote the credential file without cross-process coordination, allowing concurrent providers to overwrite each other and exposing a crash window during truncating writes. Add a same-directory temporary-file rename writer with private permissions and serialize auth read-modify-write operations with the core EffectFlock service.
Contributor
|
The following comment was made by an LLM, it may be inaccurate: Potential Duplicate FoundPR #46023: fix(opencode): serialize and atomically write auth.json This PR appears to address the same issue as the current PR #46131. Both are focused on:
This is a strong duplicate candidate that should be reviewed before merging PR #46131. You may want to check if PR #46023 was already merged or closed, or consolidate the work between them. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue for this PR
Fixes #46128
Type of change
What does this PR do?
Fixes two ways
auth.jsoncan lose credentials. Two commits, one per defect, independently reviewable.2380af8adb— stop auth writes from persisting the env snapshot.Auth.allshort-circuits toOPENCODE_AUTH_CONTENTand never reads the file.setandremoveboth started fromall(), so with that variable set they read the env snapshot and wrote it to disk — erasing anything added toauth.jsonafter the snapshot was taken.That is reachable in normal use:
control-plane/workspace.tsspawns workspace children withOPENCODE_AUTH_CONTENT: JSON.stringify(yield* auth.all()), so anauth loginor an HTTP auth-set inside a child rewrote the host's file from the child's stale copy.set/removenow read through a file-onlyread().all()andget()are unchanged — the env override still wins on reads, which workspace credential propagation depends on.3894ab2f20— write atomically under a lock.set/removeread the whole file, mutate one key, and rewrite it throughFSUtil.writeJson, which iswriteFileString+chmod— no temp+rename, no lock. Two consequences: concurrent writers to different providers lose one, and a crash mid-write truncates the whole credential file.The read-modify-write now runs under
EffectFlock.withLock("auth"), and the write goes through a newFSUtil.writeJsonAtomic(temp file + rename).EffectFlockis the existing cross-process lock (mkdir(2)atomic-create in the shared state dir, with stale detection and heartbeat) — an in-process mutex would not have fixed this, since the racing writers are separate processes.FSUtil.writeJsonis untouched for its other callers.This is the pattern already used elsewhere in the repo for weaker data.
McpAuthwraps itsmcp-auth.jsonread-modify-write in the same flock, and the TUI's scratch key-value store (context/kv.tsx) is both locked and atomic viawriteJsonAtomic. The provider credential store was the one with neither.The temp file is created with
{ flag: "wx", mode }, so0600is applied byopen(2)at create rather than by a laterchmod— no window where credentials are world-readable, andO_EXCLmeans a pre-planted symlink fails the write instead of redirecting it.How did you verify your code works?
packages/opencodeauth suite: 7 pass / 0 fail;packages/corefilesystem suite: 28 pass / 0 fail; fullpackages/opencodesuite 3398 pass / 0 failbun typecheckclean inpackages/opencodeandpackages/coreflock.withLock— keeping the atomic write and the file-only read — reddens exactlypreserves both providers from concurrent writes(6 pass / 1 fail), so the lock is load-bearing rather than incidentally covered.Auth.setcalls underEffect.all(..., { concurrency: "unbounded" }), where the async file read is the yield point that makes the race real.Known limitation
If a process is SIGKILLed between the write and the rename, a
.tmpfile holding credentials is left behind. It is created0600, so it is not world-readable, andEffect.ensuringremoves it on any normal failure. A startup sweep for stale temps is deliberately not included here — a naive sweep could delete another live process's in-flight temp, so it wants its own design.Checklist