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:
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user