fix: show actionable hint when setup-token lacks user:profile scope
When Anthropic setup-token auth is used, the OAuth usage endpoint returns 403 because setup-token only grants user:inference scope, not user:profile. This change: 1. Returns a clear, actionable error message suggesting `claude login` (full OAuth) when the scope error occurs and no web session fallback is available, instead of the generic 'HTTP 403: ...' message. 2. Adds a post-onboard note during setup-token flow warning users that usage tracking in /status requires the full OAuth flow. 3. Adds a test for the no-fallback scope error path. Refs: - https://github.com/anthropics/claude-code/issues/16075 - https://github.com/anthropics/claude-code/issues/15243 - https://github.com/anthropics/claude-code/issues/12020
This commit is contained in:
parent
da71eaebd2
commit
f47467620a
@ -55,6 +55,21 @@ export async function applyAuthChoiceAnthropic(
|
||||
provider,
|
||||
mode: "token",
|
||||
});
|
||||
|
||||
// setup-token only grants user:inference scope; usage tracking (/status)
|
||||
// requires user:profile which is only available via the full OAuth flow.
|
||||
// See: https://github.com/anthropics/claude-code/issues/16075
|
||||
await params.prompter.note(
|
||||
[
|
||||
"Note: `claude setup-token` does not include the user:profile scope.",
|
||||
"Usage tracking in /status will not be available with this token.",
|
||||
"To enable usage tracking, run `claude login` (full browser OAuth)",
|
||||
"on a machine with a browser, then use `openclaw models auth paste-token`",
|
||||
"to import the resulting token.",
|
||||
].join("\n"),
|
||||
"Usage tracking limitation",
|
||||
);
|
||||
|
||||
return { config: nextConfig };
|
||||
}
|
||||
|
||||
|
||||
@ -135,6 +135,16 @@ export async function fetchClaudeUsage(
|
||||
const web = await fetchClaudeWebUsage(sessionKey, timeoutMs, fetchFn);
|
||||
if (web) return web;
|
||||
}
|
||||
|
||||
// No web session key fallback available — return an actionable error so users
|
||||
// know how to fix usage tracking.
|
||||
return {
|
||||
provider: "anthropic",
|
||||
displayName: PROVIDER_LABELS.anthropic,
|
||||
windows: [],
|
||||
error:
|
||||
"setup-token missing user:profile scope — run `claude login` (full OAuth) to enable usage tracking",
|
||||
};
|
||||
}
|
||||
|
||||
const suffix = message ? `: ${message}` : "";
|
||||
|
||||
@ -387,4 +387,56 @@ describe("provider usage loading", () => {
|
||||
else process.env.CLAUDE_AI_SESSION_KEY = cookieSnapshot;
|
||||
}
|
||||
});
|
||||
|
||||
it("returns actionable error when scope is missing and no web session key", async () => {
|
||||
const cookieSnapshot = process.env.CLAUDE_AI_SESSION_KEY;
|
||||
const webCookieSnapshot = process.env.CLAUDE_WEB_SESSION_KEY;
|
||||
const webCookie2Snapshot = process.env.CLAUDE_WEB_COOKIE;
|
||||
delete process.env.CLAUDE_AI_SESSION_KEY;
|
||||
delete process.env.CLAUDE_WEB_SESSION_KEY;
|
||||
delete process.env.CLAUDE_WEB_COOKIE;
|
||||
try {
|
||||
const makeResponse = (status: number, body: unknown): Response => {
|
||||
const payload = typeof body === "string" ? body : JSON.stringify(body);
|
||||
const headers =
|
||||
typeof body === "string" ? undefined : { "Content-Type": "application/json" };
|
||||
return new Response(payload, { status, headers });
|
||||
};
|
||||
|
||||
const mockFetch = vi.fn<Parameters<typeof fetch>, ReturnType<typeof fetch>>(async (input) => {
|
||||
const url =
|
||||
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
||||
if (url.includes("api.anthropic.com/api/oauth/usage")) {
|
||||
return makeResponse(403, {
|
||||
type: "error",
|
||||
error: {
|
||||
type: "permission_error",
|
||||
message: "OAuth token does not meet scope requirement user:profile",
|
||||
},
|
||||
});
|
||||
}
|
||||
return makeResponse(404, "not found");
|
||||
});
|
||||
|
||||
const summary = await loadProviderUsageSummary({
|
||||
now: Date.UTC(2026, 0, 7, 0, 0, 0),
|
||||
auth: [{ provider: "anthropic", token: "sk-ant-oauth-1" }],
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
expect(summary.providers).toHaveLength(1);
|
||||
const claude = summary.providers[0];
|
||||
expect(claude?.provider).toBe("anthropic");
|
||||
expect(claude?.windows).toHaveLength(0);
|
||||
expect(claude?.error).toContain("setup-token missing user:profile scope");
|
||||
expect(claude?.error).toContain("claude login");
|
||||
} finally {
|
||||
if (cookieSnapshot === undefined) delete process.env.CLAUDE_AI_SESSION_KEY;
|
||||
else process.env.CLAUDE_AI_SESSION_KEY = cookieSnapshot;
|
||||
if (webCookieSnapshot === undefined) delete process.env.CLAUDE_WEB_SESSION_KEY;
|
||||
else process.env.CLAUDE_WEB_SESSION_KEY = webCookieSnapshot;
|
||||
if (webCookie2Snapshot === undefined) delete process.env.CLAUDE_WEB_COOKIE;
|
||||
else process.env.CLAUDE_WEB_COOKIE = webCookie2Snapshot;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user