fix(doctor): warn on opencode overrides
This commit is contained in:
@@ -58,6 +58,7 @@ cat ~/.clawdbot/clawdbot.json
|
|||||||
- Health check + restart prompt.
|
- Health check + restart prompt.
|
||||||
- Skills status summary (eligible/missing/blocked).
|
- Skills status summary (eligible/missing/blocked).
|
||||||
- Legacy config migration and normalization.
|
- Legacy config migration and normalization.
|
||||||
|
- OpenCode Zen provider override warnings (`models.providers.opencode`).
|
||||||
- Legacy on-disk state migration (sessions/agent dir/WhatsApp auth).
|
- Legacy on-disk state migration (sessions/agent dir/WhatsApp auth).
|
||||||
- State integrity and permissions checks (sessions, transcripts, state dir).
|
- State integrity and permissions checks (sessions, transcripts, state dir).
|
||||||
- Config file permission checks (chmod 600) when running locally.
|
- Config file permission checks (chmod 600) when running locally.
|
||||||
@@ -107,6 +108,12 @@ Current migrations:
|
|||||||
- `agent.model`/`allowedModels`/`modelAliases`/`modelFallbacks`/`imageModelFallbacks`
|
- `agent.model`/`allowedModels`/`modelAliases`/`modelFallbacks`/`imageModelFallbacks`
|
||||||
→ `agents.defaults.models` + `agents.defaults.model.primary/fallbacks` + `agents.defaults.imageModel.primary/fallbacks`
|
→ `agents.defaults.models` + `agents.defaults.model.primary/fallbacks` + `agents.defaults.imageModel.primary/fallbacks`
|
||||||
|
|
||||||
|
### 2b) OpenCode Zen provider overrides
|
||||||
|
If you’ve added `models.providers.opencode` (or `opencode-zen`) manually, it
|
||||||
|
overrides the built-in OpenCode Zen catalog from `@mariozechner/pi-ai`. That can
|
||||||
|
force every model onto a single API or zero out costs. Doctor warns so you can
|
||||||
|
remove the override and restore per-model API routing + costs.
|
||||||
|
|
||||||
### 3) Legacy state migrations (disk layout)
|
### 3) Legacy state migrations (disk layout)
|
||||||
Doctor can migrate older on-disk layouts into the current structure:
|
Doctor can migrate older on-disk layouts into the current structure:
|
||||||
- Sessions store + transcripts:
|
- Sessions store + transcripts:
|
||||||
|
|||||||
@@ -1001,4 +1001,39 @@ describe("doctor", () => {
|
|||||||
expect(stateNote).toBeTruthy();
|
expect(stateNote).toBeTruthy();
|
||||||
expect(String(stateNote?.[0])).toContain("CRITICAL");
|
expect(String(stateNote?.[0])).toContain("CRITICAL");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("warns about opencode provider overrides", async () => {
|
||||||
|
readConfigFileSnapshot.mockResolvedValue({
|
||||||
|
path: "/tmp/clawdbot.json",
|
||||||
|
exists: true,
|
||||||
|
raw: "{}",
|
||||||
|
parsed: {},
|
||||||
|
valid: true,
|
||||||
|
config: {
|
||||||
|
models: {
|
||||||
|
providers: {
|
||||||
|
opencode: {
|
||||||
|
api: "openai-completions",
|
||||||
|
baseUrl: "https://opencode.ai/zen/v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
issues: [],
|
||||||
|
legacyIssues: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { doctorCommand } = await import("./doctor.js");
|
||||||
|
await doctorCommand(
|
||||||
|
{ log: vi.fn(), error: vi.fn(), exit: vi.fn() },
|
||||||
|
{ nonInteractive: true, workspaceSuggestions: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
const warned = note.mock.calls.some(
|
||||||
|
([message, title]) =>
|
||||||
|
title === "OpenCode Zen" &&
|
||||||
|
String(message).includes("models.providers.opencode"),
|
||||||
|
);
|
||||||
|
expect(warned).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -98,6 +98,39 @@ function resolveMode(cfg: ClawdbotConfig): "local" | "remote" {
|
|||||||
return cfg.gateway?.mode === "remote" ? "remote" : "local";
|
return cfg.gateway?.mode === "remote" ? "remote" : "local";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function noteOpencodeProviderOverrides(cfg: ClawdbotConfig) {
|
||||||
|
const providers = cfg.models?.providers;
|
||||||
|
if (!providers) return;
|
||||||
|
|
||||||
|
// 2026-01-10: warn when OpenCode Zen overrides mask built-in routing/costs (8a194b4abc360c6098f157956bb9322576b44d51, 2d105d16f8a099276114173836d46b46cdfbdbae).
|
||||||
|
const overrides: string[] = [];
|
||||||
|
if (providers.opencode) overrides.push("opencode");
|
||||||
|
if (providers["opencode-zen"]) overrides.push("opencode-zen");
|
||||||
|
if (overrides.length === 0) return;
|
||||||
|
|
||||||
|
const lines = overrides.flatMap((id) => {
|
||||||
|
const providerEntry = providers[id];
|
||||||
|
const api =
|
||||||
|
isRecord(providerEntry) && typeof providerEntry.api === "string"
|
||||||
|
? providerEntry.api
|
||||||
|
: undefined;
|
||||||
|
return [
|
||||||
|
`- models.providers.${id} is set; this overrides the built-in OpenCode Zen catalog.`,
|
||||||
|
api ? `- models.providers.${id}.api=${api}` : null,
|
||||||
|
].filter((line): line is string => Boolean(line));
|
||||||
|
});
|
||||||
|
|
||||||
|
lines.push(
|
||||||
|
"- Remove these entries to restore per-model API routing + costs (then re-run onboarding if needed).",
|
||||||
|
);
|
||||||
|
|
||||||
|
note(lines.join("\n"), "OpenCode Zen");
|
||||||
|
}
|
||||||
|
|
||||||
async function detectClawdbotGitCheckout(
|
async function detectClawdbotGitCheckout(
|
||||||
root: string,
|
root: string,
|
||||||
): Promise<"git" | "not-git" | "unknown"> {
|
): Promise<"git" | "not-git" | "unknown"> {
|
||||||
@@ -233,6 +266,8 @@ export async function doctorCommand(
|
|||||||
cfg = normalized.config;
|
cfg = normalized.config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
noteOpencodeProviderOverrides(cfg);
|
||||||
|
|
||||||
cfg = await maybeRepairAnthropicOAuthProfileId(cfg, prompter);
|
cfg = await maybeRepairAnthropicOAuthProfileId(cfg, prompter);
|
||||||
await noteAuthProfileHealth({
|
await noteAuthProfileHealth({
|
||||||
cfg,
|
cfg,
|
||||||
|
|||||||
Reference in New Issue
Block a user