Conversation
auth.json was a flat blob describing one account, holding the whole
user('me') response. It is now { version, activeProfile, profiles,
secretsBackend }, so it can hold N accounts. Nothing puts a second one
there yet, and users see no change.
New src/lib/auth-file.ts owns the file: reading, an atomic write, the
v1 to v2 migration, and the profile accessors. credentials.ts, login,
logout, getLocalUserInfo() and the rental notice all go through it.
The migration backs the old file up as auth.json.v1.bak, runs after
ensureMigrated() as a separate step, is idempotent and single-flight,
and never throws. Fields nothing reads are dropped: email, plan,
effectivePlatformFeatures, isPaying, createdAt and proxy.groups.
Closes #1419
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Note
TL;DR —
auth.jsonwas a flat blob describing one account, holding the wholeuser('me')response. It is now{ version, activeProfile, profiles, secretsBackend }, keyed by user ID, with a migration for the three states in the wild. No UX change: the file can hold N profiles, nothing puts a second one there yet.Stacked on #1431 (Stage-1, subtask 2). Base branch is
claude/token-resolution-1418, notmaster.What changed
New
src/lib/auth-file.tsowns the file — reading, writing, migrating, and the profile accessors.credentials.ts,login,logout,getLocalUserInfo()and the rental-sunset notice all go through it, so no caller parsesauth.jsonby hand any more.{ "version": 2, "activeProfile": "<userId>", "profiles": { "<userId>": { "username": "moria", "name": null, // reserved for `--profile <name>` "organizationOwnerUserId": "…", // set => organization profile "authMethod": "token", // reserved, device flow "expiresAt": null, // reserved "hasRefreshToken": false // reserved } }, "secretsBackend": "keyring" }userId, not a display name. The key names the keyring entry in Secret storage v2 #1420, so a rename must never orphan a secret.token/proxy.passwordon the file backend. Per-profile secret keys are Secret storage v2 #1420; putting them in the profile now would force a second migration.email,plan,effectivePlatformFeatures,isPaying,createdAt, andproxy.groups.emailis also gone from theAuthJSONtype, where it was declared and never used.getLocalUserInfo()keeps its return shape, so its call sites are untouched.0600. Two CLI processes can run at once. A read-modify-write pair still races and last write wins; that is accepted, not locked around.Migration
Runs after
ensureMigrated()as a separate step, so a keyring failure and a shape failure cannot mask each other. Idempotent, single-flight, and it never throws — a migration failure must not block a command.ensureMigrated()put themkeyringtoken,proxy-passwordfileauth.json.v1.bak, and an existing backup is never overwritten.idhas no key to store a profile under. The secrets and the.bakare kept and the next command asks for a re-login; no key is invented.versionis higher than 2 is refused with a clear error, on read and before either write. No reverse migration.Behavior worth calling out
apify logoutroutes through the store instead ofrimraf-ing the file. It drops the active profile and its plaintext secrets, and removes the file once no profile is left — same observable result today, and it does not need rewriting whenauth switcharrives. It also removesauth.json.v1.bak, which would otherwise leave a plaintext token on disk after a logout.token, and reports logged out. Worth a release note;auth.json.v1.bakis the manual way back.Deviation from the issue
The issue specified that a dangling
activeProfileshould be "treated as logged out". It now throws a clear error naming the missing profile when a token is present, and reports logged out only when there is nothing to authenticate with. Returning{}whileresolveAuth()still resolves the top-level token left ~9 commands building`${undefined}/${name}`and failing with a misleadingActor with name "x" was not found— and the adjacent "noactiveProfileat all" state, which a user cannot tell apart, already threw. Only reachable from a hand-edited file; no writer produces it.Verification
pnpm run test:local— 634 passed, 4 skipped (63 files), up 3 from the base.pnpm run lint,pnpm run format,pnpm run build— clean.pnpm run update-docs— no change; no flag, arg, description, or registration moved.pnpm run test:apinot run — no token in this environment.test/local/lib/auth-file.test.ts: A/B/C on both backends, idempotency on an already-v2 file, the backup guard, the dropped fields, a corrupt file, a v1 file with noid, a danglingactiveProfile, and aversion: 3file surviving both a login and a logout byte-for-byte.test/__setup__/auth-file.tshelper. The five test files that parsedauth.jsonby hand now read a profile through it or throughgetLocalUserInfo().package.jsonandpnpm-lock.yamlare identical to the base branch).Requires Node ≥22 to run pnpm 11 locally.
Left out, deliberately
--profile, noauth switch, noauth list. The file can hold N profiles; Stage-2 (Stage-2: Login - token multi account support #1386) puts a second one there. Figure out a better auth file structure for future extensibility #567's listing part isauth list, Stage-3 (Stage-3: Multi-account UX #1384).proxy.groups.Closes #1419
🤖 Generated with Claude Code