JuliusBrussee/caveman · error
integration is degraded; run `caveman doctor --fix` before…
Error message
${profile.display_name} integration is degraded; run `caveman doctor ${agent} --fix` before changing it What it means
This error is thrown when enabling a native integration for an agent (claude/codex/gemini) whose existing integration status reports a degraded (not clean 'installed') state. The CLI refuses to layer a new enable on top of a broken integration and instead directs the user to repair it first with `caveman doctor <agent> --fix`. A bare `caveman doctor <agent>` only prints JSON stating 'degraded' without remediation, so the message deliberately points at the `--fix` flag (issue #1049).
Solutions
- Run `caveman doctor <agent> --fix` to repair the degraded integration, then retry the enable.
- Inspect `caveman doctor <agent>` JSON output to see which component is degraded before fixing.
- If repair is impossible, fully disable/revert the integration (restore config from backups or the journal) and re-enable cleanly.
- Check the agent's settings file for leftover caveman hooks/MCP entries from an older version and remove them manually, then re-run enable.
Example fix
// before caveman enable claude # throws: claude integration is degraded // after caveman doctor claude --fix caveman enable claude
Defensive patterns
Strategy: validation
Validate before calling
import { execFileSync } from "node:child_process";
const status = JSON.parse(execFileSync("caveman", ["doctor", "claude"], { encoding: "utf8" }));
if (status.installed && status.state !== "installed") {
execFileSync("caveman", ["doctor", "claude", "--fix"], { stdio: "inherit" });
}
// now safe: caveman enable claude Prevention
- Always run `caveman doctor <agent>` before enabling to confirm state is clean.
- Avoid hand-editing agent settings files while a caveman integration is enabled.
- After upgrading caveman, run `doctor --fix` for each agent to reconcile schema changes.
When it happens
Trigger: Calling `caveman enable <agent>` when `nativeIntegrationStatus(agent)` returns `existing.installed === true` and `existing.state !== 'installed'` (i.e. state is 'degraded' or similar).
Common situations: A previous partial enable/disable left the agent's config in a mixed state; a manual edit removed part of the integration; a crashed run between journal writes; upgrading caveman changed hook/MCP entry shapes so the old install no longer matches the expected canonical form.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- cave_live_eval_sandbox_profile_invalid
- caveman build: invalid .caveman/provider.json
- already exists and is not Caveman-owned; refusing to…
- already exists and is not Caveman-owned; refusing to…
- : MCP ownership journal failed; native config may already…
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/8f39487760585d1b.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:8144
if (profiles.length === 0) {
console.error(detected ? "no supported native agent detected on PATH" : `caveman enable: supported agents are claude, codex, hermes, gemini, opencode, pi, and aider (got ${target ?? ""})`);
process.exit(1);
}
const gw = gatewayURL();
for (const profile of profiles) {
const agent = profile.id as NativeAgent;
const outcome = withIntegrationLock(agent, () => {
recoverPendingNativeInstallUnlocked(agent);
if (!which(binOf(profile))) throw new Error(`${profile.display_name} not found on PATH`);
const mcpBinary = agent === "aider" ? undefined : nativeMcpBinaryRequired();
nativeProxyBinaryRequired(gw);
const existing = nativeIntegrationStatus(agent);
if (existing.installed) {
if (existing.state === "installed") return "already" as const;
// `--fix` on purpose: bare `caveman doctor <agent>` prints JSON that says
// `degraded` and nothing that says how to leave that state, so pointing
// at it alone dead-ends the user who followed this line here (#1049).
throw new Error(`${profile.display_name} integration is degraded; run \`caveman doctor ${agent} --fix\` before changing it`);
}
const mutations = nativeMutationsFor(agent, gw, mcpBinary);
const route = mutations.find((item) => typeof item.owned?.route === "string")?.owned?.route;
process.stderr.write(`caveman enable ${agent}: planned user-scoped writes\n`);
for (const mutation of mutations) process.stderr.write(` ${mutation.kind}: ${mutation.file}\n`);
if (typeof route === "string") process.stderr.write(` routing: ${route}\n`);
process.stderr.write(agent === "aider" ? " recovery MCP: unavailable in Aider\n" : ` recovery MCP: ${mcpBinary}\n`);
process.stderr.write(agent === "aider"
? ` Core: read-only ${aiderCorePath()}; lifecycle/tool interception unavailable; Ledger observational\n`
: agent === "pi"
? ` lifecycle/Core/tool rewrite: bundled Pi extension -> ${nativeHookCommand(agent)}\n`
// Codex gets the lifecycle line without the "tool rewrite" claim: since
// #1037 shrink-hook declines every Codex tool event, so promising one here
// would be the same false claim `doctor` used to report.
: agent === "codex"
? ` lifecycle/Core: ${nativeHookCommand(agent)}; command-output rewrite unavailable in Codex\n`
: ` lifecycle/Core/tool rewrite: ${nativeHookCommand(agent)}${agent === "hermes" ? " via native plugin" : ` + ${cavemanBinForHook()} shrink-hook`}\n`);
applyNativeMutations(agent, profile, mutations);View on GitHub (pinned to 3ee70a1026)