JuliusBrussee/caveman · error

Claude ANTHROPIC_BASE_URL changed after enable; refusing…

Error message

Claude ANTHROPIC_BASE_URL changed after enable; refusing destructive disable

What it means

When disabling the Claude integration, the CLI restores the `ANTHROPIC_BASE_URL` env entry in the Claude settings. If the current value exists but differs from the route caveman wrote at enable (`operation.owned.route`), the user has changed their API routing after enable. Overwriting it would destroy the user's own configuration, so the CLI refuses the destructive disable.

Solutions

  1. Set `ANTHROPIC_BASE_URL` in the Claude settings back to the exact route recorded at enable (see journal `owned.route`), then retry disable.
  2. If the new base URL is intentional, remove the caveman env entry manually from `~/.claude/settings.json` instead of using disable.
  3. Check for a second enable targeting a different gateway; disable with the journal that matches the current route.
  4. Inspect the journal file to confirm `owned.route` and diff against the settings env block.

Example fix

// before (~/.claude/settings.json)
{ "env": { "ANTHROPIC_BASE_URL": "https://my-proxy.example.com" } }

// after (restore caveman route, then disable)
{ "env": { "ANTHROPIC_BASE_URL": "https://gateway.caveman.local/v1" } }
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from "node:fs";
const settings = JSON.parse(readFileSync("~/.claude/settings.json", "utf8"));
const route = journal.operations.find(o => o.owned?.route)?.owned?.route;
const cur = settings.env?.ANTHROPIC_BASE_URL;
if (cur !== undefined && cur !== route) {
  console.error(`ANTHROPIC_BASE_URL drifted: ${cur} (journal expects ${route})`);
}

Prevention

When it happens

Trigger: `restoreNativeOperation` for a `claude-settings` operation where `currentEnv.ANTHROPIC_BASE_URL !== undefined && currentEnv.ANTHROPIC_BASE_URL !== operation.owned.route`.

Common situations: The user switched API providers/proxies (e.g. different gateway URL) while caveman routing was enabled; an upstream tool rewrote ANTHROPIC_BASE_URL; switching between caveman and another router.

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/f88fcdbae9dd5c68. Report an issue: GitHub.

Appendix: source

Thrown at packages/cli/src/index.ts:8270

  const before = nativeBackupBytes(operation);
  if (!current) {
    if (operation.before_exists) throw new Error(`${operation.file} was removed after enable; refusing destructive disable`);
    return null;
  }
  if (bytesHash(current) === operation.after_sha256) return before;

  if (operation.kind === "claude-settings") {
    const currentRoot = parseJsonFileObject(operation.file, current);
    const beforeRoot = parseJsonFileObject(operation.file, before);
    const currentEnv = currentRoot.env && typeof currentRoot.env === "object" && !Array.isArray(currentRoot.env)
      ? currentRoot.env as Record<string, unknown>
      : {};
    const beforeEnv = beforeRoot.env && typeof beforeRoot.env === "object" && !Array.isArray(beforeRoot.env)
      ? beforeRoot.env as Record<string, unknown>
      : {};
    const route = operation.owned?.route;
    if (currentEnv.ANTHROPIC_BASE_URL !== undefined && currentEnv.ANTHROPIC_BASE_URL !== route) {
      throw new Error("Claude ANTHROPIC_BASE_URL changed after enable; refusing destructive disable");
    }
    if (currentEnv.ANTHROPIC_BASE_URL === route) {
      if (beforeEnv.ANTHROPIC_BASE_URL === undefined) delete currentEnv.ANTHROPIC_BASE_URL;
      else currentEnv.ANTHROPIC_BASE_URL = beforeEnv.ANTHROPIC_BASE_URL;
    }
    // The first-party assertion is only ever written when the user carried no
    // value of their own, so restore is a plain removal — but only while the
    // value is still exactly ours; a user's later edit outlives disable.
    const assume = operation.owned?.assume_first_party;
    if (typeof assume === "string" && currentEnv[CLAUDE_ASSUME_FIRST_PARTY_ENV] === assume) {
      delete currentEnv[CLAUDE_ASSUME_FIRST_PARTY_ENV];
    }
    // Symmetric to the enable-side ENABLE_TOOL_SEARCH write: only withdraw the
    // value we introduced ourselves and only while it is still untouched, so a
    // user who set their own afterwards keeps it.
    if (beforeEnv.ENABLE_TOOL_SEARCH === undefined && currentEnv.ENABLE_TOOL_SEARCH === TOOL_SEARCH_DEFAULT) {
      delete currentEnv.ENABLE_TOOL_SEARCH;
    }

View on GitHub (pinned to 3ee70a1026)