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

  1. Restore the env block to the exact text in the journal's `owned.route_block`, then retry disable.
  2. Manually delete the entire caveman env block (from the BEGIN marker to the END marker) from the Gemini settings, then remove the journal operation.
  3. Run `caveman doctor gemini --fix` to rewrite the block to the canonical form, then disable.
  4. 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

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


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)