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
|
### Fixes
|
||||||
- Embedded runner: suppress raw API error payloads from replies. (#924) — thanks @grp06.
|
- 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
|
## 2026.1.14
|
||||||
|
|
||||||
|
|||||||
@@ -154,4 +154,81 @@ describe("resolveAuthProfileOrder", () => {
|
|||||||
});
|
});
|
||||||
expect(order).toEqual(["anthropic:work", "anthropic:default"]);
|
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, {
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
profileId: CLAUDE_CLI_PROFILE_ID,
|
profileId: CLAUDE_CLI_PROFILE_ID,
|
||||||
provider: "anthropic",
|
provider: "anthropic",
|
||||||
mode: "token",
|
mode: "oauth",
|
||||||
});
|
});
|
||||||
return { config: nextConfig };
|
return { config: nextConfig };
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ export async function applyAuthChoiceAnthropic(
|
|||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
profileId: CLAUDE_CLI_PROFILE_ID,
|
profileId: CLAUDE_CLI_PROFILE_ID,
|
||||||
provider: "anthropic",
|
provider: "anthropic",
|
||||||
mode: "token",
|
mode: "oauth",
|
||||||
});
|
});
|
||||||
return { config: nextConfig };
|
return { config: nextConfig };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,12 +91,12 @@ export async function modelsAuthSetupTokenCommand(
|
|||||||
applyAuthProfileConfig(cfg, {
|
applyAuthProfileConfig(cfg, {
|
||||||
profileId: CLAUDE_CLI_PROFILE_ID,
|
profileId: CLAUDE_CLI_PROFILE_ID,
|
||||||
provider: "anthropic",
|
provider: "anthropic",
|
||||||
mode: "token",
|
mode: "oauth",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
|
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(
|
export async function modelsAuthPasteTokenCommand(
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ export async function applyNonInteractiveAuthChoice(params: {
|
|||||||
return applyAuthProfileConfig(nextConfig, {
|
return applyAuthProfileConfig(nextConfig, {
|
||||||
profileId: CLAUDE_CLI_PROFILE_ID,
|
profileId: CLAUDE_CLI_PROFILE_ID,
|
||||||
provider: "anthropic",
|
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 () => {
|
it("auto-migrates legacy provider sections on load and writes back", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
const configPath = path.join(home, ".clawdbot", "clawdbot.json");
|
const configPath = path.join(home, ".clawdbot", "clawdbot.json");
|
||||||
|
|||||||
@@ -10,6 +10,20 @@ import {
|
|||||||
} from "./legacy.shared.js";
|
} from "./legacy.shared.js";
|
||||||
|
|
||||||
export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [
|
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",
|
id: "agent.defaults-v2",
|
||||||
describe: "Move agent config to agents.defaults and tools",
|
describe: "Move agent config to agents.defaults and tools",
|
||||||
|
|||||||
Reference in New Issue
Block a user