JuliusBrussee/caveman · error
Gemini routing block changed after enable; refusing…
Error message
Gemini routing block changed after enable; refusing destructive disable
What it means
When removing the `gemini-env` routing block, the CLI checks that the current file still contains a Gemini native env marker (`GEMINI_NATIVE_ENV_BEGIN`) only if the expected owned block is present. If markers exist but the recorded `route_block` text does not, the block was edited after enable, and removing markers or mismatched text would corrupt the file, so the disable is refused as destructive.
Solutions
- Restore the env block to the exact text in the journal's `owned.route_block`, then retry disable.
- Manually delete the entire caveman env block (from the BEGIN marker to the END marker) from the Gemini settings, then remove the journal operation.
- Run `caveman doctor gemini --fix` to rewrite the block to the canonical form, then disable.
- Diff the current file against the journal's route_block to find the edited line and revert it.
Example fix
// before (edited block, marker present, text differs) # BEGIN caveman env export ANTHROPIC_BASE_URL="https://other-gateway/v1" # END caveman env // after (restore recorded route, then disable) # BEGIN caveman env export ANTHROPIC_BASE_URL="https://gateway.caveman.local/v1" # END caveman env
Defensive patterns
Strategy: validation
Validate before calling
import { readFileSync } from "node:fs";
const text = readFileSync("~/.gemini/settings.json", "utf8");
const block = journal.operations.find(o => o.kind === "gemini-env")?.owned?.route_block;
if (text.includes("# BEGIN caveman") && block && !text.includes(block)) {
console.error("Gemini env block edited after enable; restore or remove it manually before disable");
} Prevention
- Treat the marked caveman env block as owned; edit surrounding content only.
- Re-run enable rather than editing block contents (model, base URL) in place.
- Diff the file against the journal's route_block after any tool touches Gemini settings.
When it happens
Trigger: `restoreNativeOperation` for `operation.kind === 'gemini-env'` where `text.includes(GEMINI_NATIVE_ENV_BEGIN) && !text.includes(block)` — i.e. the env file still has caveman env markers but not the exact block recorded in the journal.
Common situations: The user hand-edited lines inside the caveman env block (changed model, base URL, keys); a different caveman version rewrote the block with different formatting; another tool inserted a similar env block.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Gemini caveman MCP entry changed after enable; refusing…
- Caveman hook changed after enable; refusing destructive…
- Claude caveman MCP entry changed after enable; refusing…
- Claude ANTHROPIC_BASE_URL changed after enable; refusing…
- Gemini integration journal lacks owned routing block
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/26a536b05e4d0e75.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:8343
: {};
const installed = operation.owned?.installed_mcp;
if (servers.caveman !== undefined && JSON.stringify(servers.caveman) !== JSON.stringify(installed)) {
throw new Error("Gemini caveman MCP entry changed after enable; refusing destructive disable");
}
if (servers.caveman !== undefined) {
if (beforeServers.caveman === undefined) delete servers.caveman;
else servers.caveman = beforeServers.caveman;
}
if (Object.keys(servers).length > 0) currentRoot.mcpServers = servers;
else delete currentRoot.mcpServers;
return jsonBytes(currentRoot);
}
if (operation.kind === "gemini-env") {
const block = operation.owned?.route_block;
if (typeof block !== "string") throw new Error("Gemini integration journal lacks owned routing block");
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;View on GitHub (pinned to 3ee70a1026)