{"record":{"id":"393277a336cae768","repo":"JuliusBrussee/caveman","slug":"configpath-provider-must-be-a-json-object-refu","errorCode":null,"errorMessage":"${configPath} provider must be a JSON object; refusing to overwrite it","messagePattern":"(.+?) provider must be a JSON object; refusing to overwrite it","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":6325,"sourceCode":"    call(\"PreCompact\", { session_id: input.sessionID });\n    const stable = sessionContext(input.sessionID);\n    if (stable) output.context.push(stable);\n  },\n  dispose: async () => {\n    for (const sessionID of contexts.keys()) call(\"SessionEnd\", { session_id: sessionID });\n    contexts.clear();\n    pending.clear();\n  },\n});\n`;\n}\n\nfunction opencodeNativeMutations(gw: string, mcpBinary: string): NativeMutation[] {\n  const configPath = join(homedir(), \".config\", \"opencode\", \"opencode.json\");\n  const before = fileBytes(configPath);\n  const root = parseJsonFileObject(configPath, before);\n  if (root.provider !== undefined && (typeof root.provider !== \"object\" || root.provider === null || Array.isArray(root.provider))) {\n    throw new Error(`${configPath} provider must be a JSON object; refusing to overwrite it`);\n  }\n  if (root.mcp !== undefined && (typeof root.mcp !== \"object\" || root.mcp === null || Array.isArray(root.mcp))) {\n    throw new Error(`${configPath} mcp must be a JSON object; refusing to overwrite it`);\n  }\n  const providers = root.provider && typeof root.provider === \"object\" && !Array.isArray(root.provider) ? root.provider as Record<string, unknown> : {};\n  const previousRoutes: Record<string, unknown> = {};\n  const base = appendUrlPath(gw, \"/w/opencode\");\n  const routes = { openai: appendUrlPath(base, \"/openai/v1\"), anthropic: appendUrlPath(base, \"/anthropic/v1\") };\n  for (const [providerID, route] of Object.entries(routes)) {\n    const provider = providers[providerID] && typeof providers[providerID] === \"object\" && !Array.isArray(providers[providerID]) ? providers[providerID] as Record<string, unknown> : {};\n    const options = provider.options && typeof provider.options === \"object\" && !Array.isArray(provider.options) ? provider.options as Record<string, unknown> : {};\n    previousRoutes[providerID] = options.baseURL ?? null;\n    options.baseURL = route;\n    provider.options = options;\n    providers[providerID] = provider;\n  }\n  root.provider = providers;\n  const mcp = root.mcp && typeof root.mcp === \"object\" && !Array.isArray(root.mcp) ? root.mcp as Record<string, unknown> : {};","sourceCodeStart":6307,"sourceCodeEnd":6343,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/cli/src/index.ts#L6307-L6343","documentation":"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.","triggerScenarios":"Enabling native opencode routing when opencode.json contains \"provider\": \"anthropic\" (string), an array, null, or a number.","commonSituations":"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.","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"],"exampleFix":"// before — ~/.config/opencode/opencode.json\n{ \"provider\": \"openai\" }\n\n// after\n{ \"provider\": { \"openai\": { \"options\": { \"baseURL\": \"https://api.openai.com/v1\" } } } }","handlingStrategy":"validation","validationCode":"import { readFileSync } from \"node:fs\";\nfunction opencodeProviderShapeOk(path: string): boolean {\n  try {\n    const p = JSON.parse(readFileSync(path, \"utf8\")).provider;\n    return p === undefined || (typeof p === \"object\" && p !== null && !Array.isArray(p));\n  } catch { return true; }\n}","typeGuard":"const isJsonObject = (v: unknown): v is Record<string, unknown> =>\n  typeof v === \"object\" && v !== null && !Array.isArray(v);","tryCatchPattern":"try { nativeInstallOpencode(); } catch (e) {\n  if (e instanceof Error && /provider must be a JSON object/.test(e.message)) {\n    rewriteProviderAsMap(); nativeInstallOpencode();\n  } else throw e;\n}","preventionTips":["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"],"tags":["config","json","opencode","shape-validation","refuse-overwrite"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}