JuliusBrussee/caveman · error
Gemini integration journal lacks owned routing block
Error message
Gemini integration journal lacks owned routing block
What it means
For a `gemini-env` journal operation, the CLI needs the exact routed env block text it wrote at enable, stored in `operation.owned.route_block`. If that field is missing or not a string, the journal is malformed/incomplete and the CLI cannot safely strip the block from the Gemini env file, so it throws an internal-invariant error rather than guessing.
Solutions
- Manually remove the caveman env block (between `GEMINI_NATIVE_ENV_BEGIN`/end markers) from the Gemini settings file, then delete the journal.
- Regenerate or repair the journal so `owned.route_block` contains the exact block text written at enable, then retry disable.
- Re-enable and immediately disable with the same caveman version so a fresh, complete journal is written.
- Check the journal schema version; upgrade/downgrade caveman to the version that matches the journal format.
Example fix
// inspect the journal jq '.operations[] | select(.kind == "gemini-env") | .owned' ~/.caveman/journal.json // if route_block is null, strip the block manually then remove the operation entry
Defensive patterns
Strategy: validation
Validate before calling
const op = journal.operations.find(o => o.kind === "gemini-env");
if (!op || typeof op.owned?.route_block !== "string") {
console.error("journal gemini-env operation lacks owned.route_block; manual cleanup required");
} Type guard
function hasRouteBlock(op) {
return op.kind === "gemini-env"
&& typeof op.owned === "object" && op.owned !== null
&& typeof op.owned.route_block === "string";
} Try / catch
try {
restoreGeminiEnv(operation);
} catch (err) {
if (String(err).includes("lacks owned routing block")) {
// fall back to manual block removal + journal cleanup
}
} Prevention
- Never hand-edit or truncate the caveman journal file.
- Keep journal schema and caveman CLI versions in sync.
- Validate journal JSON with the expected schema after copying between machines.
When it happens
Trigger: `restoreNativeOperation` for `operation.kind === 'gemini-env'` where `typeof operation.owned?.route_block !== 'string'`.
Common situations: The journal file was hand-edited or truncated; a caveman version wrote an older journal schema that lacks `route_block`; the journal was copied from another machine with fields stripped.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- contents
- Gemini caveman MCP entry changed after enable; refusing…
- Gemini routing block changed after enable; refusing…
- inline_data
- was removed after enable; refusing destructive disable
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/19998dd9472140a2.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:8340
: {};
const beforeServers = beforeRoot.mcpServers && typeof beforeRoot.mcpServers === "object" && !Array.isArray(beforeRoot.mcpServers)
? beforeRoot.mcpServers as Record<string, unknown>
: {};
const installed = operation.owned?.installed_mcp;
if (servers.caveman !== undefined && JSON.stringify(servers.caveman) !== JSON.stringify(installed)) {
throw new Error("Gemini caveman MCP entry changed after enable; refusing destructive disable");
}
if (servers.caveman !== undefined) {
if (beforeServers.caveman === undefined) delete servers.caveman;
else servers.caveman = beforeServers.caveman;
}
if (Object.keys(servers).length > 0) currentRoot.mcpServers = servers;
else delete currentRoot.mcpServers;
return jsonBytes(currentRoot);
}
if (operation.kind === "gemini-env") {
const block = operation.owned?.route_block;
if (typeof block !== "string") throw new Error("Gemini integration journal lacks owned routing block");
const text = current.toString("utf8");
if (text.includes(GEMINI_NATIVE_ENV_BEGIN) && !text.includes(block)) {
throw new Error("Gemini routing block changed after enable; refusing destructive disable");
}
return Buffer.from(text.replace(`${block}\n\n`, "").replace(`\n\n${block}\n`, "\n").replace(`${block}\n`, "").replace(block, ""));
}
if (operation.kind === "opencode-plugin" || operation.kind === "pi-extension") {
throw new Error(`${operation.file} changed after enable; refusing destructive disable`);
}
if (operation.kind === "opencode-config") {
const root = parseJsonFileObject(operation.file, current);
const providers = root.provider && typeof root.provider === "object" && !Array.isArray(root.provider) ? root.provider as Record<string, unknown> : {};
const routes = operation.owned?.routes && typeof operation.owned.routes === "object" && !Array.isArray(operation.owned.routes) ? operation.owned.routes as Record<string, unknown> : {};
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> : {};
for (const providerID of ["openai", "anthropic"]) {
const provider = providers[providerID] && typeof providers[providerID] === "object" && !Array.isArray(providers[providerID]) ? providers[providerID] as Record<string, unknown> : {};
const options = provider.options && typeof provider.options === "object" && !Array.isArray(provider.options) ? provider.options as Record<string, unknown> : {};
if (options.baseURL !== undefined && options.baseURL !== routes[providerID]) {View on GitHub (pinned to 3ee70a1026)