JuliusBrussee/caveman · error
${configPath} provider must be a JSON object; refusing to ov
Error message
${configPath} provider must be a JSON object; refusing to overwrite it What it means
opencodeNativeMutations() rewrites provider baseURLs inside ~/.config/opencode/opencode.json. opencode's schema requires `provider` to be a map of provider-id to provider config; if the key exists but is not a JSON object, Caveman throws instead of clobbering it.
Source
Thrown at packages/cli/src/index.ts:6325
call("PreCompact", { session_id: input.sessionID });
const stable = sessionContext(input.sessionID);
if (stable) output.context.push(stable);
},
dispose: async () => {
for (const sessionID of contexts.keys()) call("SessionEnd", { session_id: sessionID });
contexts.clear();
pending.clear();
},
});
`;
}
function opencodeNativeMutations(gw: string, mcpBinary: string): NativeMutation[] {
const configPath = join(homedir(), ".config", "opencode", "opencode.json");
const before = fileBytes(configPath);
const root = parseJsonFileObject(configPath, before);
if (root.provider !== undefined && (typeof root.provider !== "object" || root.provider === null || Array.isArray(root.provider))) {
throw new Error(`${configPath} provider must be a JSON object; refusing to overwrite it`);
}
if (root.mcp !== undefined && (typeof root.mcp !== "object" || root.mcp === null || Array.isArray(root.mcp))) {
throw new Error(`${configPath} mcp must be a JSON object; refusing to overwrite it`);
}
const providers = root.provider && typeof root.provider === "object" && !Array.isArray(root.provider) ? root.provider as Record<string, unknown> : {};
const previousRoutes: Record<string, unknown> = {};
const base = appendUrlPath(gw, "/w/opencode");
const routes = { openai: appendUrlPath(base, "/openai/v1"), anthropic: appendUrlPath(base, "/anthropic/v1") };
for (const [providerID, route] of Object.entries(routes)) {
const provider = providers[providerID] && typeof providers[providerID] === "object" && !Array.isArray(providers[providerID]) ? providers[providerID] as Record<string, unknown> : {};
const options = provider.options && typeof provider.options === "object" && !Array.isArray(provider.options) ? provider.options as Record<string, unknown> : {};
previousRoutes[providerID] = options.baseURL ?? null;
options.baseURL = route;
provider.options = options;
providers[providerID] = provider;
}
root.provider = providers;
const mcp = root.mcp && typeof root.mcp === "object" && !Array.isArray(root.mcp) ? root.mcp as Record<string, unknown> : {};View on GitHub (pinned to 27d5a3981a)
Solutions
- Rewrite opencode.json so provider is an object keyed by provider id, each with nested options (e.g. { "openai": { "options": { "baseURL": "..." } } })
- Or delete the provider key entirely so Caveman installs its entries fresh
- Validate with `jq '.provider | type' ~/.config/opencode/opencode.json` then re-run the install
Example fix
// before — ~/.config/opencode/opencode.json
{ "provider": "openai" }
// after
{ "provider": { "openai": { "options": { "baseURL": "https://api.openai.com/v1" } } } } Defensive patterns
Strategy: validation
Validate before calling
import { readFileSync } from "node:fs";
function opencodeProviderShapeOk(path: string): boolean {
try {
const p = JSON.parse(readFileSync(path, "utf8")).provider;
return p === undefined || (typeof p === "object" && p !== null && !Array.isArray(p));
} catch { return true; }
} Type guard
const isJsonObject = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null && !Array.isArray(v);
Try / catch
try { nativeInstallOpencode(); } catch (e) {
if (e instanceof Error && /provider must be a JSON object/.test(e.message)) {
rewriteProviderAsMap(); nativeInstallOpencode();
} else throw e;
} Prevention
- Remember opencode.json `provider` is a map of provider definitions, not a provider name string
- Follow the current opencode config schema docs when hand-editing
- Validate with jq '.provider | type' before install
When it happens
Trigger: Enabling native opencode routing when opencode.json contains "provider": "anthropic" (string), an array, null, or a number.
Common situations: Confusion between the `provider` (map of provider definitions) and `model`/provider-id string settings in opencode config; hand-edits from following an outdated opencode doc; config written for a different tool version.
Related errors
- ${configPath} mcp must be a JSON object; refusing to overwri
- ${settingsPath} env must be a JSON object; refusing to overw
- ${mcpPath} mcpServers must be a JSON object; refusing to ove
- ${settingsPath} mcpServers must be a JSON object; refusing t
- caveman build: invalid .caveman/provider.json
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/393277a336cae768.
Report an issue: GitHub.