{"record":{"id":"89ba1a2bafadf3aa","repo":"JuliusBrussee/caveman","slug":"gemini-caveman-mcp-entry-changed-after-enable-ref","errorCode":null,"errorMessage":"Gemini caveman MCP entry changed after enable; refusing destructive disable","messagePattern":"Gemini caveman MCP entry changed after enable; refusing destructive disable","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":7633,"sourceCode":"    if (Object.keys(servers).length > 0) currentRoot.mcpServers = servers;\n    else delete currentRoot.mcpServers;\n    return jsonBytes(currentRoot);\n  }\n  if (operation.kind === \"codex-hooks\") {\n    return jsonBytes(removeNativeHookEntries(parseJsonFileObject(operation.file, current), \"codex\"));\n  }\n  if (operation.kind === \"gemini-settings\") {\n    const currentRoot = removeNativeHookEntries(parseJsonFileObject(operation.file, current), \"gemini\");\n    const beforeRoot = parseJsonFileObject(operation.file, before);\n    const servers = currentRoot.mcpServers && typeof currentRoot.mcpServers === \"object\" && !Array.isArray(currentRoot.mcpServers)\n      ? currentRoot.mcpServers as Record<string, unknown>\n      : {};\n    const beforeServers = beforeRoot.mcpServers && typeof beforeRoot.mcpServers === \"object\" && !Array.isArray(beforeRoot.mcpServers)\n      ? beforeRoot.mcpServers as Record<string, unknown>\n      : {};\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  }","sourceCodeStart":7615,"sourceCodeEnd":7651,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/2f49f0e1a352aa810e70056b7930aeb0b3d219b4/packages/cli/src/index.ts#L7615-L7651","documentation":"For gemini-settings, disable first strips managed hooks and then only removes mcpServers.caveman from ~/.gemini/settings.json when it still JSON-matches the entry recorded at enable time (owned.installed_mcp). A modified `caveman` entry is treated as user-owned; deleting it blindly could lose your edits, so the operation aborts with the same refusal family as the Claude MCP guard.","triggerScenarios":"`caveman disable gemini` after editing the `caveman` entry in mcpServers of ~/.gemini/settings.json - changed command, added args/env, or reformatted by another MCP-config tool.","commonSituations":"Pointing the MCP command at a wrapper; moving the caveman-mcp binary; tools that rewrite settings.json and alter key order or formatting.","solutions":["Revert mcpServers.caveman to the journalled value (owned.installed_mcp in ~/.caveman/integrations/gemini.json), then re-run `caveman disable gemini`.","Or delete the `caveman` key from mcpServers yourself, then re-run disable.","Keep personal MCP servers under names other than `caveman`."],"exampleFix":"// before ~/.gemini/settings.json\n\"mcpServers\": { \"caveman\": { \"type\": \"stdio\", \"command\": \"./wrapper\" } }\n\n// after - journalled entry restored (or key removed), then `caveman disable gemini`\n\"mcpServers\": { \"caveman\": { \"type\": \"stdio\", \"command\": \"/usr/local/bin/caveman-mcp\", \"args\": [], \"env\": {} } }","handlingStrategy":"validation","validationCode":"import { readFileSync } from 'node:fs';\nconst s = JSON.parse(readFileSync(`${process.env.HOME}/.gemini/settings.json`, 'utf8'));\nconst journal = JSON.parse(readFileSync(`${process.env.HOME}/.caveman/integrations/gemini.json`, 'utf8'));\nconst installed = journal.operations.find((o: any) => o.kind === 'gemini-settings')?.owned?.installed_mcp;\nif (s?.mcpServers?.caveman !== undefined && JSON.stringify(s.mcpServers.caveman) !== JSON.stringify(installed)) { console.error('Gemini mcpServers.caveman drifted from journal; revert or remove it before disable'); process.exit(1); }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat mcpServers.caveman in ~/.gemini/settings.json as read-only managed state","Use a different server name for personal MCP entries","Run `caveman doctor gemini --fix` after any MCP reconfiguration"],"tags":["gemini","mcp","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"}