fix(plan): a Claude plan is the one Anthropic reports now, not the sign-in's

An account signed in on Pro and upgraded to Max 5x kept showing "Pro": the
plan was read only from .credentials.json / .claude.json, whose
subscriptionType and rate-limit tiers are written at sign-in. fetchOwner
already asks GET /api/oauth/profile for a token's owner; it now also reads
organization_type / rate_limit_tier / seat_tier into a plan
(planFromProfile), remembered per account uuid (rememberLivePlan). The live
row, saved profiles (identityOf) and pool accounts (poolStatus, whose quota
refresh now asks a login's owner too) show that plan when one was seen
(shownPlan), else planLabel of the files as before.
This commit is contained in:
2026-10-28 15:09:30 +07:00
parent 33d30f5e1f
commit 0841d3e5c8
12 changed files with 171 additions and 18 deletions
+59
View File
@@ -170,6 +170,65 @@ test("a profile holding another account's token shows EWRONGACCOUNT, not that ac
}
});
test("the live plan is the profile endpoint's, not the one written at sign-in", async () => {
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const { quotas, __resetCache } = require("../lib/quota");
const root = fs.mkdtempSync(path.join(os.tmpdir(), "quota-plan-"));
const home = path.join(root, ".claude");
fs.mkdirSync(home, { recursive: true });
// Signed in on Pro; upgraded to Max 5x since — the files never learned.
fs.writeFileSync(
path.join(home, ".credentials.json"),
JSON.stringify({
claudeAiOauth: {
accessToken: "live-upgraded",
expiresAt: Date.now() + 3600_000,
subscriptionType: "pro",
rateLimitTier: "default_claude_pro",
},
})
);
fs.writeFileSync(
`${home}.json`,
JSON.stringify({ oauthAccount: { accountUuid: "uuid-up", emailAddress: "up@x.y" } })
);
const prev = { home: process.env.CLAUDE_HOME, fetch: global.fetch };
process.env.CLAUDE_HOME = home;
global.fetch = async (url) => ({
status: 200,
ok: true,
json: async () =>
url.endsWith("/profile")
? {
account: { uuid: "uuid-up", email: "up@x.y" },
organization: {
organization_type: "claude_max",
rate_limit_tier: "default_claude_max_5x",
},
}
: { five_hour: { utilization: 10, resets_at: null } },
});
try {
__resetCache();
const live = (await quotas()).tools.find((t) => t.id === "claude" && t.active);
assert.strictEqual(live.plan, "Max 5x");
// Remembered: a later read from the files alone says the same.
const { shownPlan } = require("../lib/cc-profiles");
assert.strictEqual(
shownPlan({ subscriptionType: "pro" }, { accountUuid: "uuid-up" }),
"Max 5x"
);
} finally {
if (prev.home === undefined) delete process.env.CLAUDE_HOME;
else process.env.CLAUDE_HOME = prev.home;
global.fetch = prev.fetch;
__resetCache();
fs.rmSync(root, { recursive: true, force: true });
}
});
test("the live row is the account its token belongs to, not the one .claude.json names", async () => {
const fs = require("node:fs");
const os = require("node:os");