JuliusBrussee/caveman · error · Error
MCP changes require the ownership transaction
Error message
${a.display_name} MCP changes require the ownership transaction What it means
For the kilo and qwen agents, MCP add/remove is not allowed through the plain per-agent mutation helpers because these agents use a dedicated ownership transaction (journal + canonical marker path, see errors 483/484) instead of direct config edits. The remove switch-case throws to enforce that invariants are maintained only through the transactional path.
Solutions
- Use the ownership-transaction command/flow for kilo and qwen MCP changes (the install/uninstall with journaling)
- For other agents, keep using the direct remove path — the restriction is agent-specific
- Update automation scripts to dispatch kilo/qwen to the transactional API
Example fix
// before
removeMcpAgent({ id: "kilo", display_name: "Kilo" }, serverName);
// after
await runMcpOwnershipTransaction("kilo", serverName, /* remove */ true); Defensive patterns
Strategy: validation
Validate before calling
const TRANSACTIONAL_AGENTS = new Set(["kilo", "qwen"]);
if (TRANSACTIONAL_AGENTS.has(agent.id)) {
await runMcpOwnershipTransaction(agent.id, serverName);
} else {
removeMcpDirect(agent, serverName);
} Type guard
function requiresOwnershipTransaction(agentId: string): boolean {
return agentId === "kilo" || agentId === "qwen";
} Try / catch
try {
removeMcpForAgent(agent, serverName);
} catch (e) {
if ((e as Error).message.includes("ownership transaction")) {
await runMcpOwnershipTransaction(agent.id, serverName);
} else throw e;
} Prevention
- Dispatch MCP mutations per-agent; route kilo/qwen through the transactional API
- Update legacy scripts that predate the ownership transaction
- Prefer the CLI's top-level install/uninstall commands over direct mutation helpers
When it happens
Trigger: Calling the MCP remove/uninstall path with agent 'kilo' or 'qwen' instead of the transactional ownership flow — i.e. invoking a direct mutation API or CLI subcommand that bypasses the ownership transaction.
Common situations: Scripts written for other agents (codex, gemini, opencode) applied to kilo/qwen; older automation predating the ownership-transaction requirement; attempting manual config cleanup with the generic remove command.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- MCP transaction failed and rolled back
- MCP transaction failed and safe recovery was blocked: …
- MCP transaction postflight mismatch
- : MCP ownership journal failed; native config may already…
- caveman-cloud MCP changed after setup; refusing destructive…
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/924d223b2dda794b.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:11049
function uninstallMcpForAgent(a: AgentProfile, serverName = "caveman"): boolean {
switch (a.id) {
case "claude": {
const claude = which("claude");
if (!claude) return true; // agent itself is gone; dropping the marker is all that's left
try {
execPortableIgnore(claude, ["mcp", "remove", serverName]);
} catch {
// not registered — fine, still a successful uninstall.
}
return true;
}
case "codex":
return removeMcpCodexToml(serverName);
case "opencode":
return removeMcpJson(join(homedir(), ".config", "opencode", "opencode.json"), ["mcp", serverName]);
case "kilo":
case "qwen":
throw new Error(`${a.display_name} MCP changes require the ownership transaction`);
case "gemini":
return removeMcpJson(geminiSettingsPath(), ["mcpServers", serverName]);
case "hermes":
return removeMcpHermesYaml(serverName);
case "openclaw":
return uninstallMcpOpenClaw(serverName);
default:
return false;
}
}
// removeMcpCodexToml drops the exact [mcp_servers.<name>] block mcpInstall wrote
// (header + its command/args lines, up to the next section or EOF).
function removeMcpCodexToml(serverName = "caveman"): boolean {
const path = join(codexHomeDir(), "config.toml");
let existing = "";
try {
existing = readFileSync(path, "utf8");View on GitHub (pinned to 3ee70a1026)