This commit is contained in:
Dewaldt Huysamen 2026-01-30 17:59:19 +02:00 committed by GitHub
commit bc3a38289a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 77 additions and 0 deletions

View File

@ -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 };
}

View File

@ -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}` : "";

View File

@ -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;
}
});
});