Skip to content

kj doctor

kj doctor answers “why isn’t this working?” — and, where it can, fixes it for you. It’s the command you run when kj run behaves unexpectedly, and the command CI runs before kj run to fail fast on a broken runner.

kj doctor runs a battery of checks across the whole environment and prints each as pass / warn / fail, then auto-remediates the ones it safely can. The checks span: system dependencies (OS-level binaries), the config files Karajan expects, the agent CLIs and their versions, Node version, the ports Karajan/HU-Board/Sonar use, API tokens, SonarQube container health, MCP server health, skills availability, the ~/.karajan directory layout, RTK, and CI wiring.

When a check fails and a fix is known and non-destructive, kj doctor applies it (e.g. creating a missing directory, starting a stopped Sonar container) and re-reports it as “auto-fixed”. Invasive remediations prompt for confirmation on a TTY; --yes auto-confirms them for CI. --check-only makes it purely diagnostic — detect, never touch.

A second mode, --project, runs only project-aware checks: it inspects the current repo for stack signals, verifies the external tools each signal needs are present, checks write permissions, .env consistency, that the gh remote is reachable, the state of the repository itself (the order it was set up in, whether the contract travels to a clone, whether this clone declares an identity), and the pinned GitHub Actions.

That last one is the maintenance that pinning moved onto kj. The workflows kj harden writes pin their actions to a commit SHA, because a moving tag lets whoever controls an action run code with your repo’s secrets. The trade is that nobody delivers the upstream patch either, so kj doctor asks GitHub what each tag points at today and warns with both SHAs and the literal line that updates the pin. It asks through gh when you have it (your own credentials, 5000 calls an hour) and falls back to an unauthenticated call, which GitHub caps at 60 an hour per IP. When it cannot reach the API it says so rather than reporting green: an unchecked pin is not a current pin. This check lives here and deliberately not in kj check, because a gate that needs the network is a gate that fails on a plane.

The exit code is meaningful: a blocking failure (a check that’s fail or timeout) makes kj doctor exit non-zero, so a CI step can gate on it.

  • kj run is misbehaving — kj doctor first; it usually names the cause (wrong Node, missing CLI, dead Sonar).
  • Before a CI kj run — kj doctor --check-only --json as a gating step.
  • After a machine change — new Node, reinstalled CLI, OS upgrade: kj doctor confirms nothing regressed.
  • Validating a specific project — kj doctor --project before the first kj run in an unfamiliar repo.
  • Unattended remediation — kj doctor --yes in a provisioning script to fix what’s auto-fixable without prompts.
  • Initial setup — kj doctor checks an existing setup; it doesn’t create one. Run kj init first.
  • Installing missing audit tools — kj doctor will tell you semgrep is missing and suggest kj install-tools; it doesn’t install them itself. Use kj install-tools.
  • As a health monitor loop — it’s a point-in-time diagnostic, not a watcher. Don’t poll it on a timer; run it when something changed.
FlagDefaultWhen to flip itInteraction
--check-onlyoff (auto-fixes)You want diagnosis without any changes — inspecting a sensitive machine, or just curious what’s wrong.Mutually exclusive in spirit with --yes (nothing to confirm if nothing is fixed).
-y, --yesoffCI / unattended — auto-confirm invasive remediations that would otherwise prompt.No effect with --check-only.
--jsonoff (human output)Programmatic consumption — a CI step parses the result and decides.Pairs with --check-only for a pure machine-readable diagnostic.
--verboseoffYou want the fix hint and timing for every check, not just failures — useful when a pass is suspiciously slow.—
--projectoff (full sweep)Validate one repo’s readiness (stack signals, per-signal tools, write perms, .env, gh remote) rather than the whole machine.Replaces the broad checks with project-only ones; combine with --json for a CI pre-flight.

Typical: something’s wrong, fix what you can

Section titled “Typical: something’s wrong, fix what you can”
Terminal window
kj doctor

What happens: every check prints with an icon; auto-fixable failures (missing dir, stopped Sonar) are repaired and marked “auto-fixed”; invasive fixes prompt. The summary line says “All checks passed (N auto-fixed)” or lists the blockers. Exit code reflects whether a blocker remains.

Terminal window
kj doctor --check-only --json

What happens: pure diagnosis, no changes, machine-readable. The CI step inspects the JSON (or just the non-zero exit on a blocking failure) and stops the pipeline before wasting a kj run on a broken runner.

Terminal window
cd ../unfamiliar-repo && kj doctor --project --verbose

What happens: only project-aware checks run — detected stack, the external tools that stack needs, write permissions, .env consistency, gh remote reachability — with fix hints for each. Tells you exactly what to provision before kj run here.

Terminal window
kj doctor --yes

What happens: every auto-fixable and invasive-but-known remediation is applied without prompting. Used at the end of a machine-provisioning script so the runner is kj run-ready.

kj doctor is built as a check pipeline: each domain (system, binaries, node, ports, tokens, sonar, mcp-health, skills, dir-setup, ci, rtk, project) contributes a set of independent checks with a known status and, where applicable, a remediation. The design decision that matters is detect-and-remediate as one pass, not two: a check doesn’t just report “Sonar down”, it carries the knowledge of how to bring it up, so the same run that finds the problem can fix it. This is why --check-only is a deliberate opt-out rather than the default — the common case is “I want this working”, not “I want a report”.

The split between the full sweep and --project encodes a real distinction: machine-level health (is Node new enough, are the CLIs installed) is stable and rarely changes, while project readiness (does this repo’s stack need lighthouse, is the gh remote reachable) changes every time you cd somewhere new. Bundling them would make the common pre-kj run check needlessly slow; separating them lets CI run the cheap project check on every job and the full sweep only at provisioning time. The meaningful exit code exists for exactly that CI use — kj doctor is designed to be a gate, not just a console nicety.

  • kj init — creates the setup kj doctor verifies.
  • kj install-tools — installs the external tools kj doctor reports missing.
  • External tools — what each tool doctor checks for actually does and why it matters.
  • kj config — inspect the config whose validity doctor checks.