fix: normalize Claude CLI auth mode to oauth (#855)
Thanks @sebslight. Co-authored-by: Sebastian <sebslight@gmail.com>
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
|
||||
### Fixes
|
||||
- Embedded runner: suppress raw API error payloads from replies. (#924) — thanks @grp06.
|
||||
- Slack: respect `channels.slack.requireMention` default when resolving channel mention gating. (#850) — thanks @evalexpr.
|
||||
- Auth: normalize Claude Code CLI profile mode to oauth and auto-migrate config. (#855) — thanks @sebslight.
|
||||
|
||||
## 2026.1.14
|
||||
|
||||
|
||||
@@ -154,4 +154,81 @@ describe("resolveAuthProfileOrder", () => {
|
||||
});
|
||||
expect(order).toEqual(["anthropic:work", "anthropic:default"]);
|
||||
});
|
||||
|
||||
it("mode: oauth config accepts both oauth and token credentials (issue #559)", () => {
|
||||
const now = Date.now();
|
||||
const storeWithBothTypes: AuthProfileStore = {
|
||||
version: 1,
|
||||
profiles: {
|
||||
"anthropic:oauth-cred": {
|
||||
type: "oauth",
|
||||
provider: "anthropic",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: now + 60_000,
|
||||
},
|
||||
"anthropic:token-cred": {
|
||||
type: "token",
|
||||
provider: "anthropic",
|
||||
token: "just-a-token",
|
||||
expires: now + 60_000,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const orderOauthCred = resolveAuthProfileOrder({
|
||||
store: storeWithBothTypes,
|
||||
provider: "anthropic",
|
||||
cfg: {
|
||||
auth: {
|
||||
profiles: {
|
||||
"anthropic:oauth-cred": { provider: "anthropic", mode: "oauth" },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(orderOauthCred).toContain("anthropic:oauth-cred");
|
||||
|
||||
const orderTokenCred = resolveAuthProfileOrder({
|
||||
store: storeWithBothTypes,
|
||||
provider: "anthropic",
|
||||
cfg: {
|
||||
auth: {
|
||||
profiles: {
|
||||
"anthropic:token-cred": { provider: "anthropic", mode: "oauth" },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(orderTokenCred).toContain("anthropic:token-cred");
|
||||
});
|
||||
|
||||
it("mode: token config rejects oauth credentials (issue #559 root cause)", () => {
|
||||
const now = Date.now();
|
||||
const storeWithOauth: AuthProfileStore = {
|
||||
version: 1,
|
||||
profiles: {
|
||||
"anthropic:oauth-cred": {
|
||||
type: "oauth",
|
||||
provider: "anthropic",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: now + 60_000,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const order = resolveAuthProfileOrder({
|
||||
store: storeWithOauth,
|
||||
provider: "anthropic",
|
||||
cfg: {
|
||||
auth: {
|
||||
profiles: {
|
||||
"anthropic:oauth-cred": { provider: "anthropic", mode: "token" },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(order).not.toContain("anthropic:oauth-cred");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -84,7 +84,7 @@ export async function applyAuthChoiceAnthropic(
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: CLAUDE_CLI_PROFILE_ID,
|
||||
provider: "anthropic",
|
||||
mode: "token",
|
||||
mode: "oauth",
|
||||
});
|
||||
return { config: nextConfig };
|
||||
}
|
||||
@@ -146,7 +146,7 @@ export async function applyAuthChoiceAnthropic(
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: CLAUDE_CLI_PROFILE_ID,
|
||||
provider: "anthropic",
|
||||
mode: "token",
|
||||
mode: "oauth",
|
||||
});
|
||||
return { config: nextConfig };
|
||||
}
|
||||
|
||||
@@ -91,12 +91,12 @@ export async function modelsAuthSetupTokenCommand(
|
||||
applyAuthProfileConfig(cfg, {
|
||||
profileId: CLAUDE_CLI_PROFILE_ID,
|
||||
provider: "anthropic",
|
||||
mode: "token",
|
||||
mode: "oauth",
|
||||
}),
|
||||
);
|
||||
|
||||
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
|
||||
runtime.log(`Auth profile: ${CLAUDE_CLI_PROFILE_ID} (anthropic/token)`);
|
||||
runtime.log(`Auth profile: ${CLAUDE_CLI_PROFILE_ID} (anthropic/oauth)`);
|
||||
}
|
||||
|
||||
export async function modelsAuthPasteTokenCommand(
|
||||
|
||||
@@ -270,7 +270,7 @@ export async function applyNonInteractiveAuthChoice(params: {
|
||||
return applyAuthProfileConfig(nextConfig, {
|
||||
profileId: CLAUDE_CLI_PROFILE_ID,
|
||||
provider: "anthropic",
|
||||
mode: "token",
|
||||
mode: "oauth",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -200,6 +200,46 @@ describe("legacy config detection", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
it("auto-migrates claude-cli auth profile mode to oauth", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const configPath = path.join(home, ".clawdbot", "clawdbot.json");
|
||||
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
||||
await fs.writeFile(
|
||||
configPath,
|
||||
JSON.stringify(
|
||||
{
|
||||
auth: {
|
||||
profiles: {
|
||||
"anthropic:claude-cli": { provider: "anthropic", mode: "token" },
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
vi.resetModules();
|
||||
try {
|
||||
const { loadConfig } = await import("./config.js");
|
||||
const cfg = loadConfig();
|
||||
expect(cfg.auth?.profiles?.["anthropic:claude-cli"]?.mode).toBe("oauth");
|
||||
|
||||
const raw = await fs.readFile(configPath, "utf-8");
|
||||
const parsed = JSON.parse(raw) as {
|
||||
auth?: { profiles?: Record<string, { mode?: string }> };
|
||||
};
|
||||
expect(parsed.auth?.profiles?.["anthropic:claude-cli"]?.mode).toBe("oauth");
|
||||
expect(
|
||||
warnSpy.mock.calls.some(([msg]) => String(msg).includes("Auto-migrated config")),
|
||||
).toBe(true);
|
||||
} finally {
|
||||
warnSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
it("auto-migrates legacy provider sections on load and writes back", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const configPath = path.join(home, ".clawdbot", "clawdbot.json");
|
||||
|
||||
@@ -10,6 +10,20 @@ import {
|
||||
} from "./legacy.shared.js";
|
||||
|
||||
export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [
|
||||
{
|
||||
id: "auth.anthropic-claude-cli-mode-oauth",
|
||||
describe: "Switch anthropic:claude-cli auth profile mode to oauth",
|
||||
apply: (raw, changes) => {
|
||||
const auth = getRecord(raw.auth);
|
||||
const profiles = getRecord(auth?.profiles);
|
||||
if (!profiles) return;
|
||||
const claudeCli = getRecord(profiles["anthropic:claude-cli"]);
|
||||
if (!claudeCli) return;
|
||||
if (claudeCli.mode !== "token") return;
|
||||
claudeCli.mode = "oauth";
|
||||
changes.push('Updated auth.profiles["anthropic:claude-cli"].mode → "oauth".');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "agent.defaults-v2",
|
||||
describe: "Move agent config to agents.defaults and tools",
|
||||
|
||||
Reference in New Issue
Block a user