JuliusBrussee/caveman · error
caveman agent: invalid .caveman/provider.json
Error message
caveman agent: invalid .caveman/provider.json
What it means
`localProviderModel` reads `.caveman/provider.json` to pick up a locally pinned model. If parsing the file throws for any reason other than ENOENT (missing file), it rethrows as `caveman agent: invalid .caveman/provider.json`. A missing file is tolerated (returns undefined), but a present, unreadable, or malformed file fails the run.
Solutions
- Fix the JSON so it parses, e.g. `{ "model": "openai/gpt-5.4-mini" }` — validate with `cat .caveman/provider.json | python3 -m json.tool` or `jq .`.
- Delete the file if you don't need a local pin (`rm .caveman/provider.json`) — its absence is valid and falls back to env credentials.
- If it is a directory or has wrong permissions, remove/repair it so the path is a readable regular file.
- Set `CAVE_MODEL` instead and remove the file to avoid the fragile local pin.
Example fix
// before (.caveman/provider.json)
{ "model": "anthropic/claude-haiku-4-5", }
// after
{ "model": "anthropic/claude-haiku-4-5" } Defensive patterns
Strategy: validation
Validate before calling
import { readFileSync } from "node:fs";
function providerJsonIsValid(root: string): boolean {
try { JSON.parse(readFileSync(`${root}/.caveman/provider.json`, "utf8")); return true; }
catch (e) { return (e as NodeJS.ErrnoException).code === "ENOENT"; }
} Type guard
null
Try / catch
try {
await agent.run(task);
} catch (e) {
if ((e as Error).message === "caveman agent: invalid .caveman/provider.json") {
console.error("Fix or delete .caveman/provider.json (must be valid JSON: {\"model\": \"provider/model\"})");
} else throw e;
} Prevention
- After hand-editing .caveman/provider.json, run it through a JSON parser (jq) before running the agent.
- Prefer CAVE_MODEL over the local file pin; delete the file when unused.
- Ensure .caveman/provider.json is a readable regular file, not a directory or truncated write.
When it happens
Trigger: `.caveman/provider.json` exists but contains invalid JSON (trailing comma, comments, truncation), is a directory, or is unreadable due to permissions — any read/parse error whose code is not ENOENT.
Common situations: Hand-editing the file and leaving broken JSON; a failed/crashed write leaving a truncated file; creating `.caveman/provider.json` as a directory by mistake; permission changes after copying a project.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
Related errors
- caveman build: invalid .caveman/provider.json
- local learn snapshot is unreadable at
- hooks. must be an array; refusing to overwrite it
- hooks must be a JSON object; refusing to overwrite it
- is not a JSON object
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/e6d32809829a20f6.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/runtime.ts:3274
throw new Error(
configured.length === 0
? "caveman agent: no supported provider credential found"
: "caveman agent: multiple provider credentials found; set CAVE_MODEL once",
);
}
const [provider, id] = configured[0]!;
const model = models.getModel(provider, id);
if (!model) throw new Error(`caveman agent: baseline model unavailable: ${provider}/${id}`);
return model;
}
function localProviderModel(rootDir: string): string | undefined {
try {
const parsed = JSON.parse(readFileSync(resolve(rootDir, ".caveman/provider.json"), "utf8")) as { model?: unknown };
return typeof parsed.model === "string" ? parsed.model : undefined;
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
throw new Error("caveman agent: invalid .caveman/provider.json");
}
}
/**
* Point a model at the gateway when the gateway speaks its dialect.
*
* The three routes below are the only ones the Caveman gateway proxies. Every
* other provider Pi can reach (xai, groq, bedrock, openrouter, …) keeps its own
* base URL, and `routed: false` is what tells the caller this request is
* third-party traffic: no account key, no telemetry headers, no `optimized`.
*/
function routeModelThroughCave(
model: Model<Api>,
gatewayURL: string,
): { model: Model<Api>; routed: boolean } {
const prefix: Record<string, string> = {
anthropic: "anthropic",
// Pi provider clients append endpoint paths relative to these versionedView on GitHub (pinned to 3ee70a1026)