{"record":{"id":"f7b9f78612946a2e","repo":"JuliusBrussee/caveman","slug":"caveman-agent-invalid-caveman-provider-json","errorCode":null,"errorMessage":"caveman agent: invalid .caveman/provider.json","messagePattern":"caveman agent: invalid \\.caveman/provider\\.json","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/claude-runtime.ts","lineNumber":484,"sourceCode":"    : process.env.CAVE_MODEL ?? localModel(rootDir) ??\n      (process.env.ANTHROPIC_API_KEY ? \"anthropic/claude-haiku-4-5\" : undefined);\n  if (configured === undefined) throw new Error(\"cave_claude_model_required\");\n  const separator = configured.indexOf(\"/\");\n  if (separator < 1 || configured.slice(0, separator) !== \"anthropic\") {\n    throw new Error(\"cave_claude_provider_unsupported\");\n  }\n  return normalizeClaudeModel(configured.slice(separator + 1));\n}\n\nfunction localModel(rootDir: string): string | undefined {\n  try {\n    const parsed = JSON.parse(readFileSync(resolve(rootDir, \".caveman/provider.json\"), \"utf8\")) as {\n      model?: unknown;\n    };\n    return typeof parsed.model === \"string\" ? parsed.model : undefined;\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n    throw new Error(\"caveman agent: invalid .caveman/provider.json\");\n  }\n}\n\nfunction normalizeClaudeModel(model: string): string {\n  return model.startsWith(\"anthropic/\") ? model.slice(\"anthropic/\".length) : model;\n}\n\nfunction claudeEffort(reasoning: AgentDefinition[\"reasoning\"]): \"low\" | \"medium\" | \"high\" {\n  if (reasoning === \"medium\") return \"medium\";\n  if (reasoning === \"high\") return \"high\";\n  return \"low\";\n}\n\nfunction claudeReasoningOptions(\n  model: string,\n  reasoning: AgentDefinition[\"reasoning\"],\n  outputMaxTokens: number | undefined,\n): Pick<ClaudeSDKOptions, \"thinking\" | \"effort\"> {","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/claude-runtime.ts#L466-L502","documentation":"localModel() reads <rootDir>/.caveman/provider.json to find a locally configured model. ENOENT (file absent) is fine and returns undefined, but any other failure — JSON.parse syntax errors, EACCES, EISDIR — rethrows as \"caveman agent: invalid .caveman/provider.json\". A present-but-corrupt provider file is treated as a hard configuration defect, not silently ignored.","triggerScenarios":"provider.json exists but contains invalid JSON (trailing comma, single quotes, comment, BOM, truncated by a concurrent write), is a directory, or is unreadable due to permissions; then a Claude-lane model resolution runs and reaches localModel().","commonSituations":"Hand-editing provider.json and leaving a trailing comma; a tool writing the file non-atomically so a crash leaves it half-written; committing a template with placeholder text; file owned by another user in shared CI.","solutions":["Fix the JSON: cat .caveman/provider.json | jq . to find the syntax error, then correct it.","Ensure the file is a plain object with a string model: { \"model\": \"anthropic/claude-sonnet-4-5\" }.","If you don't want local provider config, delete the file entirely — absence is handled gracefully.","Make writers atomic (write temp + rename) so crashes can't leave partial files."],"exampleFix":"// before: .caveman/provider.json\n{ \"model\": \"anthropic/claude-sonnet-4-5\", } // trailing comma → JSON.parse throws\n\n// after\n{ \"model\": \"anthropic/claude-sonnet-4-5\" }","handlingStrategy":"validation","validationCode":"function providerModelOrNull(rootDir: string): string | null {\n  try {\n    const parsed = JSON.parse(readFileSync(join(rootDir, \".caveman\", \"provider.json\"), \"utf8\"));\n    return typeof parsed?.model === \"string\" ? parsed.model : null;\n  } catch {\n    return null; // treat unreadable/invalid as absent at YOUR layer; decide explicitly\n  }\n}","typeGuard":"function isValidProviderJson(value: unknown): value is { model: string } {\n  return typeof value === \"object\" && value !== null\n    && typeof (value as { model?: unknown }).model === \"string\";\n}","tryCatchPattern":null,"preventionTips":["Validate .caveman/provider.json with `jq .` after every hand edit.","Write config files atomically (tmp file + rename) so crashes never leave truncated JSON.","Remember ENOENT is fine but any other read/parse failure is fatal — delete the file rather than leaving it corrupt."],"tags":["configuration","json","filesystem"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}