{"record":{"id":"26a536b05e4d0e75","repo":"JuliusBrussee/caveman","slug":"gemini-routing-block-changed-after-enable-refusin","errorCode":null,"errorMessage":"Gemini routing block changed after enable; refusing destructive disable","messagePattern":"Gemini routing block changed after enable; refusing destructive disable","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":7648,"sourceCode":"      : {};\n    const installed = operation.owned?.installed_mcp;\n    if (servers.caveman !== undefined && JSON.stringify(servers.caveman) !== JSON.stringify(installed)) {\n      throw new Error(\"Gemini caveman MCP entry changed after enable; refusing destructive disable\");\n    }\n    if (servers.caveman !== undefined) {\n      if (beforeServers.caveman === undefined) delete servers.caveman;\n      else servers.caveman = beforeServers.caveman;\n    }\n    if (Object.keys(servers).length > 0) currentRoot.mcpServers = servers;\n    else delete currentRoot.mcpServers;\n    return jsonBytes(currentRoot);\n  }\n  if (operation.kind === \"gemini-env\") {\n    const block = operation.owned?.route_block;\n    if (typeof block !== \"string\") throw new Error(\"Gemini integration journal lacks owned routing block\");\n    const text = current.toString(\"utf8\");\n    if (text.includes(GEMINI_NATIVE_ENV_BEGIN) && !text.includes(block)) {\n      throw new Error(\"Gemini routing block changed after enable; refusing destructive disable\");\n    }\n    return Buffer.from(text.replace(`${block}\\n\\n`, \"\").replace(`\\n\\n${block}\\n`, \"\\n\").replace(`${block}\\n`, \"\").replace(block, \"\"));\n  }\n  if (operation.kind === \"opencode-plugin\" || operation.kind === \"pi-extension\") {\n    throw new Error(`${operation.file} changed after enable; refusing destructive disable`);\n  }\n  if (operation.kind === \"opencode-config\") {\n    const root = parseJsonFileObject(operation.file, current);\n    const providers = root.provider && typeof root.provider === \"object\" && !Array.isArray(root.provider) ? root.provider as Record<string, unknown> : {};\n    const routes = operation.owned?.routes && typeof operation.owned.routes === \"object\" && !Array.isArray(operation.owned.routes) ? operation.owned.routes as Record<string, unknown> : {};\n    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> : {};\n    for (const providerID of [\"openai\", \"anthropic\"]) {\n      const provider = providers[providerID] && typeof providers[providerID] === \"object\" && !Array.isArray(providers[providerID]) ? providers[providerID] as Record<string, unknown> : {};\n      const options = provider.options && typeof provider.options === \"object\" && !Array.isArray(provider.options) ? provider.options as Record<string, unknown> : {};\n      if (options.baseURL !== undefined && options.baseURL !== routes[providerID]) {\n        throw new Error(`OpenCode ${providerID} baseURL changed after enable; refusing destructive disable`);\n      }\n      if (previousRoutes[providerID] === null || previousRoutes[providerID] === undefined) delete options.baseURL;","sourceCodeStart":7630,"sourceCodeEnd":7666,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/2f49f0e1a352aa810e70056b7930aeb0b3d219b4/packages/cli/src/index.ts#L7630-L7666","documentation":"For gemini-env, restore only proceeds when the file either no longer contains the begin marker '# >>> caveman:native-routing' (block already gone) or still contains the exact journalled owned.route_block. If the marker is present but the exact block string is not, the routing block was edited after enable; removing by marker boundaries could eat your changes, so disable refuses.","triggerScenarios":"`caveman disable gemini` after editing lines inside the caveman routing block in ~/.gemini/.env - changed the base URL, reformatted, added or reordered lines - so the file no longer contains the byte-exact block recorded in the journal.","commonSituations":"Hand-tweaking the gateway URL inside the marked block instead of via caveman config; formatters or secret-injection tools rewriting .env files.","solutions":["Restore the block bytes from the journal: owned.route_block of the gemini-env operation in ~/.caveman/integrations/gemini.json.","Or delete the entire marked block (from '# >>> caveman:native-routing' to its '# <<< ...' end) by hand, then re-run `caveman disable gemini` - with the marker gone the guard passes.","Prefer `caveman doctor gemini --fix` to re-canonicalize the block before disabling."],"exampleFix":"# before ~/.gemini/.env (block hand-edited, no longer byte-exact)\n# >>> caveman:native-routing\nexport GEMINI_API_BASE=\"https://my-tweak\"   # edited line\n# <<< caveman:native-routing\n\n# after - block removed entirely by hand (or restored from journal), then\n$ caveman disable gemini","handlingStrategy":"validation","validationCode":"import { readFileSync } from 'node:fs';\nconst env = readFileSync(`${process.env.HOME}/.gemini/.env`, 'utf8');\nif (env.includes('# >>> caveman:native-routing')) {\n  const journal = JSON.parse(readFileSync(`${process.env.HOME}/.caveman/integrations/gemini.json`, 'utf8'));\n  const block = journal.operations.find((o: any) => o.kind === 'gemini-env')?.owned?.route_block;\n  if (typeof block === 'string' && !env.includes(block)) { console.error('gemini routing block drifted; restore it or remove the whole marked block before disable'); process.exit(1); }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Edit gateway routing through caveman config, never inside the marked .env block","Point formatters and secret injectors away from ~/.gemini/.env","Run `caveman doctor gemini --fix` before disable to re-canonicalize the block"],"tags":["gemini","env-file","disable","config-drift"],"backgroundTag":"config-drift-refusal","analyzedSha":"2f49f0e1a352aa810e70056b7930aeb0b3d219b4","analyzedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}