fix(cc-config): read the live account's quota from the CLI, and ask for every account at once
The quota check failed with `invalid_grant` for the account the user was logged into - the one profile they would try first. An OAuth refresh token is single use, so every refresh Claude Code performs on its own account retires the copy sitting in that account's saved profile: the stored credentials of the LIVE account are the one set guaranteed to be rejected, however recently the profile was saved. Confirmed on a real install - the saved profile held a refresh token three rotations behind `.credentials.json`. `cc-profiles.liveCredentialsFor` now matches a profile's accountUuid against the live credentials and, on a hit, the usage call uses what the CLI is holding. That token is never refreshed and that file is never written: rotating it would log the user out of their own terminal. An expired live token answers ELIVEEXPIRED - using Claude Code once makes the CLI refresh it - and a genuinely spent refresh token is now reported as ESTALE with what to do about it, instead of echoing Anthropic's raw 400. Opening the Profiles modal also asks for every saved account at once, rather than one button per row: comparing accounts is the only reason the panel exists. The guard against rewriting credentials over and over is no longer "one row at a time" but the rules above - the live account costs a plain read, and any other profile refreshes only once its token has actually expired, which then leaves it valid for hours of further opens. Each row resolves on its own, so a superseded account still lets its neighbours report.
This commit is contained in:
@@ -156,6 +156,29 @@ function freeName(base) {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The LIVE credentials when `stored` is the account logged in right now, else
|
||||
* null.
|
||||
*
|
||||
* A saved profile is a copy, and an OAuth refresh token is single use: every
|
||||
* time Claude Code refreshes the account it is logged into, the copy sitting in
|
||||
* the profile file is retired. So for the active account the saved credentials
|
||||
* are the one set guaranteed NOT to work, and `.credentials.json` — which the
|
||||
* CLI keeps current — is the only usable source. Nothing here writes: rotating
|
||||
* the token the live session is holding would log the user out of their own
|
||||
* terminal.
|
||||
*
|
||||
* @param stored a profile as returned by `readProfile`
|
||||
* @returns the live `claudeAiOauth` block, or null when this is not that account
|
||||
*/
|
||||
function liveCredentialsFor(stored) {
|
||||
const live = readActiveAuth();
|
||||
if (!live.oauth) return null;
|
||||
const mine = identityOf(live).accountUuid;
|
||||
const theirs = identityOf({ oauth: stored.credentials, account: stored.account }).accountUuid;
|
||||
return mine && mine === theirs ? live.oauth : null;
|
||||
}
|
||||
|
||||
/** Read one stored profile, or null when it does not exist. */
|
||||
function readProfile(name) {
|
||||
const stored = readJson(profileFile(name));
|
||||
@@ -388,6 +411,7 @@ module.exports = {
|
||||
// Token-bearing reads/writes: for cc-usage.js, never for a route.
|
||||
readProfile,
|
||||
writeProfile,
|
||||
liveCredentialsFor,
|
||||
// exported for tests
|
||||
profilesDir,
|
||||
configPaths,
|
||||
|
||||
Reference in New Issue
Block a user