{"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":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":8328,"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":8310,"sourceCodeEnd":8346,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L8310-L8346","documentation":"Same ownership check as the Claude case but for Gemini: on disable, the CLI verifies `mcpServers.caveman` in the Gemini settings matches the JSON caveman installed at enable (`operation.owned.installed_mcp`). A divergent entry means the config was modified after enable, so the CLI refuses to destructively remove it.","triggerScenarios":"`restoreNativeOperation` for a Gemini settings operation where `servers.caveman !== undefined` and `JSON.stringify(servers.caveman) !== JSON.stringify(operation.owned.installed_mcp)`.","commonSituations":"Manual edits to the Gemini settings `mcpServers` block; Gemini CLI or another installer rewrote the caveman entry; caveman version upgrade changed the MCP entry definition.","solutions":["Restore `mcpServers.caveman` to the exact object from the journal's `owned.installed_mcp`, then retry disable.","Manually remove the `mcpServers.caveman` key from the Gemini settings file, then re-run disable.","Run `caveman doctor gemini --fix` to reconcile the entry with the journal, then disable.","Remove the stale journal operation so disable stops asserting ownership over the modified entry."],"exampleFix":"// before (~/.gemini/settings.json)\n\"mcpServers\": { \"caveman\": { \"command\": \"node\", \"args\": [\"/custom/path/caveman-mcp.js\"] } }\n\n// after (restore recorded form)\n\"mcpServers\": { \"caveman\": { \"command\": \"caveman\", \"args\": [\"mcp\"] } }","handlingStrategy":"validation","validationCode":"import { readFileSync } from \"node:fs\";\nconst settings = JSON.parse(readFileSync(\"~/.gemini/settings.json\", \"utf8\"));\nconst op = journal.operations.find(o => o.owned?.installed_mcp);\nif (settings.mcpServers?.caveman &&\n    JSON.stringify(settings.mcpServers.caveman) !== JSON.stringify(op.owned.installed_mcp)) {\n  console.error(\"Gemini mcpServers.caveman drifted from journal; restore before disable\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Avoid hand-editing the caveman MCP entry in Gemini settings.","Re-run enable (which rewrites the entry) instead of patching it in place.","Verify the entry matches the journal after any Gemini CLI update that touches mcpServers."],"tags":["mcp","gemini","safety-guard","disable","config-drift"],"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"}