JuliusBrussee/caveman · error · Error
OpenCode ${providerID} baseURL changed after enable; refusin
Error message
OpenCode ${providerID} baseURL changed after enable; refusing destructive disable What it means
Thrown while disabling or repairing the OpenCode integration when the live provider options.baseURL in opencode.json (usually ~/.config/opencode/opencode.json) is defined but no longer equals the route Caveman installed, which is recorded in the journal under owned.routes (keyed by provider id 'openai' or 'anthropic'). This is a merge guard: `caveman disable opencode` would otherwise reset baseURL to the pre-enable value and silently destroy your routing change. It only fires when the file hash already differs from after_sha256, i.e. someone edited the config after enable.
Source
Thrown at packages/cli/src/index.ts:7664
const text = current.toString("utf8");
if (text.includes(GEMINI_NATIVE_ENV_BEGIN) && !text.includes(block)) {
throw new Error("Gemini routing block changed after enable; refusing destructive disable");
}
return Buffer.from(text.replace(`${block}\n\n`, "").replace(`\n\n${block}\n`, "\n").replace(`${block}\n`, "").replace(block, ""));
}
if (operation.kind === "opencode-plugin" || operation.kind === "pi-extension") {
throw new Error(`${operation.file} changed after enable; refusing destructive disable`);
}
if (operation.kind === "opencode-config") {
const root = parseJsonFileObject(operation.file, current);
const providers = root.provider && typeof root.provider === "object" && !Array.isArray(root.provider) ? root.provider as Record<string, unknown> : {};
const routes = operation.owned?.routes && typeof operation.owned.routes === "object" && !Array.isArray(operation.owned.routes) ? operation.owned.routes as Record<string, unknown> : {};
const previousRoutes = operation.owned?.previous_routes && typeof operation.owned.previous_routes === "object" && !Array.isArray(operation.owned.previous_routes) ? operation.owned.previous_routes as Record<string, unknown> : {};
for (const providerID of ["openai", "anthropic"]) {
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> : {};
if (options.baseURL !== undefined && options.baseURL !== routes[providerID]) {
throw new Error(`OpenCode ${providerID} baseURL changed after enable; refusing destructive disable`);
}
if (previousRoutes[providerID] === null || previousRoutes[providerID] === undefined) delete options.baseURL;
else options.baseURL = previousRoutes[providerID];
if (Object.keys(options).length > 0) provider.options = options;
else delete provider.options;
if (Object.keys(provider).length > 0) providers[providerID] = provider;
else delete providers[providerID];
}
if (Object.keys(providers).length > 0) root.provider = providers;
else delete root.provider;
const mcp = root.mcp && typeof root.mcp === "object" && !Array.isArray(root.mcp) ? root.mcp as Record<string, unknown> : {};
if (mcp.caveman !== undefined && JSON.stringify(mcp.caveman) !== JSON.stringify(operation.owned?.installed_mcp)) {
throw new Error("OpenCode caveman MCP entry changed after enable; refusing destructive disable");
}
if (mcp.caveman !== undefined) {
if (operation.owned?.previous_mcp === null || operation.owned?.previous_mcp === undefined) delete mcp.caveman;
else mcp.caveman = operation.owned.previous_mcp;
}View on GitHub (pinned to 2f49f0e1a3)
Solutions
- Set the drifted provider's options.baseURL back to the exact value in owned.routes of ~/.caveman/integrations/opencode.json (or delete the options.baseURL key entirely), then rerun `caveman disable opencode`
- If you intend to keep the new baseURL: port any other config you care about out of opencode.json, then manually remove the caveman provider routing and mcp.caveman entry and delete the journal file
- Re-run `caveman enable opencode` to re-journal the current routing, then disable before editing again
Example fix
// before: ~/.config/opencode/opencode.json was edited after enable
{ "provider": { "openai": { "options": { "baseURL": "http://localhost:4000/v1" } } } }
// after: baseURL matches the journaled route (owned.routes.openai), disable proceeds
{ "provider": { "openai": { "options": { "baseURL": "http://127.0.0.1:3777/w/opencode/v1" } } } } Defensive patterns
Strategy: validation
Validate before calling
// Verify provider baseURL still matches the journaled route before disabling
import { readFileSync } from 'node:fs';
const home = process.env.CAVEMAN_HOME ?? `${process.env.HOME}/.caveman`;
const journal = JSON.parse(readFileSync(`${home}/integrations/opencode.json`, 'utf8'));
const cfg = JSON.parse(readFileSync(process.env.OPENCODE_CONFIG ?? `${process.env.HOME}/.config/opencode/opencode.json`, 'utf8'));
for (const op of journal.operations) {
for (const id of ['openai', 'anthropic']) {
const live = cfg.provider?.[id]?.options?.baseURL;
if (live !== undefined && live !== op.owned?.routes?.[id]) {
throw new Error(`refusing: ${id} baseURL drifted`);
}
}
} Try / catch
catch (err) { if (err.message.includes('baseURL changed after enable')) { /* reset options.baseURL to journal value or delete it, then retry once */ } else throw err; } Prevention
- Do not edit provider.options.baseURL in opencode.json while caveman routing is enabled — disable first, change, re-enable
- Keep custom provider endpoints under a different provider key than openai/anthropic
- Check `caveman doctor opencode` output before scripted disables
When it happens
Trigger: `caveman disable opencode` (or `caveman doctor opencode --fix`) after manually changing provider.openai.options.baseURL or provider.anthropic.options.baseURL in opencode.json to point at a different endpoint (e.g. a direct OpenAI/Anthropic URL, a different proxy port) while a Caveman route was installed.
Common situations: Temporarily switching opencode to a direct provider URL while Caveman routing was enabled; another routing tool (LiteLLM, OpenRouter config) rewriting provider options; editing baseURL to debug, then later trying to disable Caveman cleanly.
Related errors
- OpenCode caveman MCP entry changed after enable; refusing de
- ${agent} ${event} Caveman hook changed after enable; refusin
- ${operation.file} was removed after enable; refusing destruc
- Claude ANTHROPIC_BASE_URL changed after enable; refusing des
- Claude caveman MCP entry changed after enable; refusing dest
AI-assisted analysis of JuliusBrussee/caveman@2f49f0e1a3 (2026-08-18).
Data as JSON: /api/errors/a6e6e6f7fcae04b7.
Report an issue: GitHub.