{"record":{"id":"f88fcdbae9dd5c68","repo":"JuliusBrussee/caveman","slug":"claude-anthropic-base-url-changed-after-enable-re","errorCode":null,"errorMessage":"Claude ANTHROPIC_BASE_URL changed after enable; refusing destructive disable","messagePattern":"Claude ANTHROPIC_BASE_URL changed after enable; refusing destructive disable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":8270,"sourceCode":"  const before = nativeBackupBytes(operation);\n  if (!current) {\n    if (operation.before_exists) throw new Error(`${operation.file} was removed after enable; refusing destructive disable`);\n    return null;\n  }\n  if (bytesHash(current) === operation.after_sha256) return before;\n\n  if (operation.kind === \"claude-settings\") {\n    const currentRoot = parseJsonFileObject(operation.file, current);\n    const beforeRoot = parseJsonFileObject(operation.file, before);\n    const currentEnv = currentRoot.env && typeof currentRoot.env === \"object\" && !Array.isArray(currentRoot.env)\n      ? currentRoot.env as Record<string, unknown>\n      : {};\n    const beforeEnv = beforeRoot.env && typeof beforeRoot.env === \"object\" && !Array.isArray(beforeRoot.env)\n      ? beforeRoot.env as Record<string, unknown>\n      : {};\n    const route = operation.owned?.route;\n    if (currentEnv.ANTHROPIC_BASE_URL !== undefined && currentEnv.ANTHROPIC_BASE_URL !== route) {\n      throw new Error(\"Claude ANTHROPIC_BASE_URL changed after enable; refusing destructive disable\");\n    }\n    if (currentEnv.ANTHROPIC_BASE_URL === route) {\n      if (beforeEnv.ANTHROPIC_BASE_URL === undefined) delete currentEnv.ANTHROPIC_BASE_URL;\n      else currentEnv.ANTHROPIC_BASE_URL = beforeEnv.ANTHROPIC_BASE_URL;\n    }\n    // The first-party assertion is only ever written when the user carried no\n    // value of their own, so restore is a plain removal — but only while the\n    // value is still exactly ours; a user's later edit outlives disable.\n    const assume = operation.owned?.assume_first_party;\n    if (typeof assume === \"string\" && currentEnv[CLAUDE_ASSUME_FIRST_PARTY_ENV] === assume) {\n      delete currentEnv[CLAUDE_ASSUME_FIRST_PARTY_ENV];\n    }\n    // Symmetric to the enable-side ENABLE_TOOL_SEARCH write: only withdraw the\n    // value we introduced ourselves and only while it is still untouched, so a\n    // user who set their own afterwards keeps it.\n    if (beforeEnv.ENABLE_TOOL_SEARCH === undefined && currentEnv.ENABLE_TOOL_SEARCH === TOOL_SEARCH_DEFAULT) {\n      delete currentEnv.ENABLE_TOOL_SEARCH;\n    }","sourceCodeStart":8252,"sourceCodeEnd":8288,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L8252-L8288","documentation":"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.","triggerScenarios":"`restoreNativeOperation` for a `claude-settings` operation where `currentEnv.ANTHROPIC_BASE_URL !== undefined && currentEnv.ANTHROPIC_BASE_URL !== operation.owned.route`.","commonSituations":"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.","solutions":["Set `ANTHROPIC_BASE_URL` in the Claude settings back to the exact route recorded at enable (see journal `owned.route`), then retry disable.","If the new base URL is intentional, remove the caveman env entry manually from `~/.claude/settings.json` instead of using disable.","Check for a second enable targeting a different gateway; disable with the journal that matches the current route.","Inspect the journal file to confirm `owned.route` and diff against the settings env block."],"exampleFix":"// before (~/.claude/settings.json)\n{ \"env\": { \"ANTHROPIC_BASE_URL\": \"https://my-proxy.example.com\" } }\n\n// after (restore caveman route, then disable)\n{ \"env\": { \"ANTHROPIC_BASE_URL\": \"https://gateway.caveman.local/v1\" } }","handlingStrategy":"validation","validationCode":"import { readFileSync } from \"node:fs\";\nconst settings = JSON.parse(readFileSync(\"~/.claude/settings.json\", \"utf8\"));\nconst route = journal.operations.find(o => o.owned?.route)?.owned?.route;\nconst cur = settings.env?.ANTHROPIC_BASE_URL;\nif (cur !== undefined && cur !== route) {\n  console.error(`ANTHROPIC_BASE_URL drifted: ${cur} (journal expects ${route})`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Disable the caveman integration before switching API providers or proxy URLs.","Record journal `owned.route` and compare whenever ANTHROPIC_BASE_URL is edited.","Keep provider changes in a separate env layer rather than editing Claude settings directly."],"tags":["env-var","routing","safety-guard","claude","disable"],"backgroundTag":"invalid-state-transition","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}