{"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":7327,"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":7309,"sourceCodeEnd":7345,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L7309-L7345","documentation":"`opencodeNativeMutations()` modifies ~/.config/opencode/opencode.json to add Caveman provider routes. Before writing, it validates that an existing top-level `provider` key is a plain JSON object; any other type (string, array, null) aborts the mutation so the CLI never clobbers user configuration it cannot merge.","triggerScenarios":"Running native opencode setup when the parsed opencode.json has `provider` defined but not as a non-null plain object (`typeof root.provider !== \"object\" || null || Array.isArray`).","commonSituations":"Hand-edited opencode.json where provider was written as a string or list; a config generator emitted provider: null as a placeholder; a merge/sync tool flattened the provider map; copy-paste of an invalid snippet.","solutions":["Open opencode.json (path shown in the message) and change `provider` to a JSON object","Back up the file before editing: cp opencode.json opencode.json.bak","If provider is an array, convert entries to object properties keyed by provider id","Re-run `caveman setup` after fixing the shape"],"exampleFix":"// before: ~/.config/opencode/opencode.json\n{ \"provider\": [\"openai\", \"anthropic\"] }\n// after\n{ \"provider\": { \"openai\": {}, \"anthropic\": {} } }","handlingStrategy":"type-guard","validationCode":"// Pre-flight on opencode.json:\nconst cfg = JSON.parse(fs.readFileSync(opencodeConfigPath, 'utf8'));\nif (cfg.provider !== undefined && (typeof cfg.provider !== 'object' || cfg.provider === null || Array.isArray(cfg.provider))) {\n  console.error(`Fix ${opencodeConfigPath}: provider must be an object`);\n}","typeGuard":"function isProviderMap(v: unknown): v is Record<string, unknown> {\n  return typeof v === 'object' && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  await cavemanSetup(['opencode']);\n} catch (e) {\n  if (String(e.message).includes('provider must be a JSON object')) {\n    // fix provider shape in opencode.json, then retry\n  }\n  throw e;\n}","preventionTips":["Keep `provider` as a keyed object in opencode.json (providerID -> config)","Back up opencode.json before hand edits","Validate config against opencode's schema after using generators or sync tools","Avoid placeholder nulls in generated config"],"tags":["config","json-shape","opencode","setup"],"backgroundTag":"config-type-mismatch","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}