Merge pull request #822 from clawdbot/fix/credential-fallback-761
fix: treat credential validation failures as auth errors for fallback
This commit is contained in:
@@ -7,12 +7,13 @@
|
|||||||
- Memory: allow custom OpenAI-compatible embedding endpoints for memory search (remote baseUrl/apiKey/headers). (#819 — thanks @mukhtharcm)
|
- Memory: allow custom OpenAI-compatible embedding endpoints for memory search (remote baseUrl/apiKey/headers). (#819 — thanks @mukhtharcm)
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
- Fallback: treat credential validation failures ("no credentials found", "no API key found") as auth errors that trigger model fallback. (#822 — thanks @sebslight)
|
||||||
- Telegram: persist polling update offsets across restarts to avoid duplicate updates. (#739 — thanks @thewilloftheshadow)
|
- Telegram: persist polling update offsets across restarts to avoid duplicate updates. (#739 — thanks @thewilloftheshadow)
|
||||||
- Discord: avoid duplicate message/reaction listeners on monitor reloads. (#744 — thanks @thewilloftheshadow)
|
- Discord: avoid duplicate message/reaction listeners on monitor reloads. (#744 — thanks @thewilloftheshadow)
|
||||||
- System events: include local timestamps when events are injected into prompts. (#245 — thanks @thewilloftheshadow)
|
- System events: include local timestamps when events are injected into prompts. (#245 — thanks @thewilloftheshadow)
|
||||||
- Cron: accept `jobId` aliases for cron update/run/remove params in gateway validation. (#252 — thanks @thewilloftheshadow)
|
- Cron: accept `jobId` aliases for cron update/run/remove params in gateway validation. (#252 — thanks @thewilloftheshadow)
|
||||||
- Models/Google: normalize Gemini 3 model ids to preview variants before runtime selection. (#795 — thanks @thewilloftheshadow)
|
- Models/Google: normalize Gemini 3 model ids to preview variants before runtime selection. (#795 — thanks @thewilloftheshadow)
|
||||||
- TUI: keep the last streamed response instead of replacing it with “(no output)”. (#747 — thanks @thewilloftheshadow)
|
- TUI: keep the last streamed response instead of replacing it with "(no output)". (#747 — thanks @thewilloftheshadow)
|
||||||
- Slack: accept slash commands with or without leading `/` for custom command configs. (#798 — thanks @thewilloftheshadow)
|
- Slack: accept slash commands with or without leading `/` for custom command configs. (#798 — thanks @thewilloftheshadow)
|
||||||
- Onboarding/Configure: refuse to proceed with invalid configs; run `clawdbot doctor` first to avoid wiping custom fields. (#764 — thanks @mukhtharcm)
|
- Onboarding/Configure: refuse to proceed with invalid configs; run `clawdbot doctor` first to avoid wiping custom fields. (#764 — thanks @mukhtharcm)
|
||||||
- Anthropic: merge consecutive user turns (preserve newest metadata) before validation to avoid “Incorrect role information” errors. (#804 — thanks @ThomsenDrake)
|
- Anthropic: merge consecutive user turns (preserve newest metadata) before validation to avoid “Incorrect role information” errors. (#804 — thanks @ThomsenDrake)
|
||||||
|
|||||||
@@ -102,6 +102,48 @@ describe("runWithModelFallback", () => {
|
|||||||
expect(run.mock.calls[1]?.[1]).toBe("claude-haiku-3-5");
|
expect(run.mock.calls[1]?.[1]).toBe("claude-haiku-3-5");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("falls back on credential validation errors", async () => {
|
||||||
|
const cfg = makeCfg();
|
||||||
|
const run = vi
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValueOnce(
|
||||||
|
new Error('No credentials found for profile "anthropic:claude-cli".'),
|
||||||
|
)
|
||||||
|
.mockResolvedValueOnce("ok");
|
||||||
|
|
||||||
|
const result = await runWithModelFallback({
|
||||||
|
cfg,
|
||||||
|
provider: "anthropic",
|
||||||
|
model: "claude-opus-4",
|
||||||
|
run,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.result).toBe("ok");
|
||||||
|
expect(run).toHaveBeenCalledTimes(2);
|
||||||
|
expect(run.mock.calls[1]?.[0]).toBe("anthropic");
|
||||||
|
expect(run.mock.calls[1]?.[1]).toBe("claude-haiku-3-5");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back on missing API key errors", async () => {
|
||||||
|
const cfg = makeCfg();
|
||||||
|
const run = vi
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValueOnce(new Error("No API key found for profile openai."))
|
||||||
|
.mockResolvedValueOnce("ok");
|
||||||
|
|
||||||
|
const result = await runWithModelFallback({
|
||||||
|
cfg,
|
||||||
|
provider: "openai",
|
||||||
|
model: "gpt-4.1-mini",
|
||||||
|
run,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.result).toBe("ok");
|
||||||
|
expect(run).toHaveBeenCalledTimes(2);
|
||||||
|
expect(run.mock.calls[1]?.[0]).toBe("anthropic");
|
||||||
|
expect(run.mock.calls[1]?.[1]).toBe("claude-haiku-3-5");
|
||||||
|
});
|
||||||
|
|
||||||
it("appends the configured primary as a last fallback", async () => {
|
it("appends the configured primary as a last fallback", async () => {
|
||||||
const cfg = makeCfg({
|
const cfg = makeCfg({
|
||||||
agents: {
|
agents: {
|
||||||
|
|||||||
@@ -361,6 +361,9 @@ const ERROR_PATTERNS = {
|
|||||||
"token has expired",
|
"token has expired",
|
||||||
/\b401\b/,
|
/\b401\b/,
|
||||||
/\b403\b/,
|
/\b403\b/,
|
||||||
|
// Credential validation failures should trigger fallback (#761)
|
||||||
|
"no credentials found",
|
||||||
|
"no api key found",
|
||||||
],
|
],
|
||||||
format: [
|
format: [
|
||||||
"invalid_request_error",
|
"invalid_request_error",
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import { formatDurationMs } from "../infra/format-duration.js";
|
|||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
import { resolveTelegramAccount } from "./accounts.js";
|
import { resolveTelegramAccount } from "./accounts.js";
|
||||||
import { createTelegramBot } from "./bot.js";
|
import { createTelegramBot } from "./bot.js";
|
||||||
|
import { makeProxyFetch } from "./proxy.js";
|
||||||
import {
|
import {
|
||||||
readTelegramUpdateOffset,
|
readTelegramUpdateOffset,
|
||||||
writeTelegramUpdateOffset,
|
writeTelegramUpdateOffset,
|
||||||
} from "./update-offset-store.js";
|
} from "./update-offset-store.js";
|
||||||
import { makeProxyFetch } from "./proxy.js";
|
|
||||||
import { startTelegramWebhook } from "./webhook.js";
|
import { startTelegramWebhook } from "./webhook.js";
|
||||||
|
|
||||||
export type MonitorTelegramOpts = {
|
export type MonitorTelegramOpts = {
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ describe("telegram update offset store", () => {
|
|||||||
updateId: 421,
|
updateId: 421,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(
|
expect(await readTelegramUpdateOffset({ accountId: "primary" })).toBe(
|
||||||
await readTelegramUpdateOffset({ accountId: "primary" }),
|
421,
|
||||||
).toBe(421);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user