Skip to content

koryph doctor

koryph doctor checks the health of the koryph installation. Without --project it runs global checks against ~/.koryph. With --project <id> it runs project-scoped checks against the named project.

Usage

koryph doctor [--json] [--fix]
koryph doctor --project <project-id> [--json]
Flag Description
--project <id> Run project-scoped checks for the named registered project
--json Emit the report as a JSON object instead of a text table
--fix Remove zombie slot lease files and stale demand heartbeats (global mode)

Exit codes

Code Meaning
0 All checks passed
1 One or more warnings
2 One or more errors

Checks

layout

Verifies that ~/.koryph exists with the required subdirectory skeleton (registry.d/, quota/, slots/). A missing home directory or subdirectory is an error — run koryph init to repair.

binaries

Checks that git, claude, and bd are on PATH. Missing binaries are warnings (the CLI can still run for some commands).

beads-version

Checks that the resolved bd binary (the KORYPH_BD_BIN override, else bd on PATH) is new enough to emit the parent field koryph relies on. Older bd releases (≤ 1.0.3) omit parent from bd list --json, which silently degrades koryph — the TUI Queue tab renders flat (every issue a top-level row, no epic folds) and parent-linked views mis-group — with no error anywhere else. A too-old bd is a warning whose remediation is tailored to how bd was installed: a bd resolved from a /nix/store/ path is flagged as nix-provided, with advice to bump the flake/devshell pin (or set KORYPH_BD_BIN to a newer bd as a stopgap); otherwise it suggests a package-manager upgrade. The same preflight prints a one-line warning at the top of koryph run and in the koryph tui header, so the degradation is never silent.

In project mode (koryph doctor --project <id>), when the stale bd is nix-provided and the project's flake.nix pins a beads input, doctor makes a concrete upgrade offer: it names the exact nix flake lock --update-input <input> command and the project directory. Adding --fix runs that command (re-locking flake.lock to the pinned version); you then reload the devshell (direnv reload or re-enter nix develop) so the running bd picks up the new version. This turns a stale-lock warning into a one-command remedy.

registry

Parses every *.json file in registry.d/. A file that is not valid JSON is an error.

auth-mode

Reports, for every registered account, its effective auth mode (subscription, api-key, or oauth-token), where its credential comes from, and its identity fingerprint prefix — never the credential value itself:

  • subscription (the default) needs no credential and is always ok.
  • api-key/oauth-token with a vault credential reports the vault provider and key reference; with an env credential it reports the environment variable name (never its value).
  • A non-subscription account with no credential configured is an error. One with a credential but no identity fingerprint yet is a warning — the fingerprint is captured at the account's next dispatch.
  • A per-runtime account override (runtime_accounts) is reported separately only when its resolved auth mode, credential, or fingerprint diverges from the record's own; a runtime that just mirrors the record is not repeated.

governor

Validates governor.json when present. An absent file is fine — koryph falls back to the default cap. A corrupt or zero-value file is an error/warning respectively.

memory-floor

Flags any pool in governor.json with no explicit min_free_memory_mb (koryph-4rk6.1) — the exact gap ("anthropic had a floor, personal/work didn't") behind the 2026-07-21 OOM incident. A pool with a positive floor or an explicit negative (deliberately disabled) is ok; a pool at the raw, never-set 0 is a warning naming the koryph governor set --min-free-memory-mb command to pin one. koryph run self-heals this on every startup by backfilling DefaultMinFreeMemoryMB onto any such pool, so in practice this check mostly catches drift from a governor.json edited or restored outside of koryph.

zombie-leases

Scans slots/*.json for lease files whose tracked PID is no longer alive. Each zombie is a warning; pass --fix to remove them immediately.

koryph doctor --fix

stale-demand

Scans slots/demand/*.json for demand heartbeats whose engine PID is dead or whose updated_at timestamp is older than 10 minutes. Each stale entry is a warning; pass --fix to remove them.

quota-calibration

Reads each quota/<account>.json and checks whether both window_ceiling_usd and weekly_ceiling_usd are greater than zero. An uncalibrated account is a warning — the governor runs in advisory-only mode until calibrated:

koryph quota calibrate --account personal --window 5h \
    --observed-usd 4.20 --observed-pct 21

vault-providers

When ~/.koryph/vault.json is present, checks that the first binary in each provider's fetch template (e.g. pass-cli for ProtonPass, op for 1Password) is on PATH. A missing binary is a warning.

proxy

For every registered project with an agent_proxy block configured (see Headroom integration), runs four checks:

  • loopback — confirms base_url is still an http:// loopback address (the registry already refuses to load a non-loopback value, but a hand-edited record could bypass that; a non-loopback base_url is an error).
  • healthGET <base_url><health> must return 2xx. Unreachable or non-2xx is an error; an unconfigured health path is a warning.
  • pin — when pin is set, compares it against the "pin" field in the health response. A mismatch is an error with refuse-to-route guidance (a different proxy version is running than the registry expects); a health response with no "pin" field is a warning (cannot verify).
  • routing verification — compares koryph's own ledger count of dispatches routed to this proxy's arm against the proxy's self-reported forwarded-request counter (GET <base_url><stats>, stats defaulting to /stats). This exists because health+pin only prove the proxy process is up and correctly versioned, not that dispatched traffic is actually flowing through it — a proxy that is healthy and correctly pinned but silently bypassed (e.g. a ChildEnvSpec/ANTHROPIC_BASE_URL wiring regression) would otherwise pass doctor clean while every dispatch goes direct. Outcomes:
  • No proxied-arm dispatches recorded yet → ok (nothing to verify).
  • Stats endpoint unreachable, non-2xx, or its JSON body has no field doctor recognizes as a request counter (a requests/request_count/ total_requests-style name, top level or nested one level) → warning naming the limitation. Never a silent pass.
  • Dispatches recorded but the proxy reports zero upstream-seen requests → error — configured but not in path; refuse-to-route until the wiring is fixed.
  • Dispatches recorded and the proxy reports a nonzero count → ok.

Project-mode checks

When --project <id> is given, koryph doctor runs against the project's repository root instead of ~/.koryph. These checks mirror what koryph onboard validates structurally (no network calls, no subprocess invocations):

project-config

Loads and validates koryph.project.json. A missing or corrupt file is an error.

git-repo

Verifies that .git exists at the project root. A missing .git is an error.

hooks-wiring

Checks .claude/settings.json for the three koryph hook markers: bd prime (SessionStart), agent-boundary-guard.sh (PreToolUse Bash), and worktree-guard.sh (PreToolUse Bash|Edit|Write). Each missing marker is a warning — run koryph rules install to repair.

signing

Inspects the project's signing block in koryph.project.json: - Absent block → ok (signing not configured). - Provider set but public_key not captured → warning (run koryph signing setup). - Invalid config shape (caught by project-config parse) → error.

protected-paths

Validates the protected_paths list for empty entries (error) and duplicate entries (warning).

stalled-runs

Scans the project ledger (.plan-logs/koryph/) for runs in running status where any non-terminal slot has not been updated for more than 30 minutes. Each stalled slot is a warning. Investigate manually — stalled agents may need to be stopped with koryph stop.

koryph doctor --project koryph

orphan-worktrees

Lists git worktrees registered under the project's worktree root (default <parent>/<repo>-worktrees/) whose branch starts with agent/ but have no corresponding active slot in any currently-running ledger run. Each orphan is a warning. Koryph never removes a dirty worktree automatically; review and remove manually:

git worktree remove --force path/to/orphan

ci-assets

Checks whether the koryph gate pipeline CI workflow is installed and up to date with the current template rendering.

The check is skipped (ok) when the project has no recognisable forge remote (not a GitHub repository or no git remote configured).

When the gate pipeline is absent or its content drifted from what koryph ci setup would render, the finding is a warning with the exact remediation command:

koryph ci setup --project <id>

When the installed file matches the current template the finding is ok.

The gate pipeline path is .github/workflows/koryph-gate.yml for GitHub projects. Install or update it with:

koryph ci setup --project <id>
koryph ci setup --project <id> --force   # overwrite a locally modified file

container release

When release.container is configured, doctor validates the separately generated GHCR image-release workflow:

  • container-release-block compares the release.container block with .github/workflows/container.yml. A configured container without the file, or the file without the configuration, is a warning. Re-run koryph release setup after configuring a container; remove a no-longer-used workflow when disabling one. Invalid container fields are reported by the project-config error before doctor renders any expected workflow.
  • container-dockerfile checks for the repository-root Dockerfile used by the generated workflow's default context: . build. A missing file (or a directory at that path) is a warning; add a regular Dockerfile or disable the container release.
  • container-workflow-drift compares the installed workflow with the current renderer output. A mismatch is a warning with the exact koryph release setup remediation. The check is skipped when no container release is configured or when the consistency check already found the file missing.
  • container-publication-gate confirms that the publish job depends on a detected Release PR merge. A hand edit that could publish on an ordinary main push is a warning; rerun koryph release setup to restore the gate.
  • container-permission-scope confirms that packages: write, OIDC, and attestation permissions live only on the post-gate publish job. A write permission at workflow scope or another job, or a missing publish permission, is a warning; rerun koryph release setup to restore least privilege.

See Releasing projects for the GHCR, Dockerfile, tag, signing, SBOM, and provenance contract.

epic-validations

Reports open epics carrying validation:parked (exceeded max_rounds, awaiting an operator decision) or validation:degraded (validator infra failure) as warnings, each naming koryph epic validate <id> as the recovery command. See Epic validation for the full label vocabulary.

unvalidated-epics

Reports open epics whose children are all closed but that never themselves closed — validation was never triggered, or the docs-bead close-after-docs path stalled after the docs bead closed. This is the offline counterpart to the live loop's hourly health-patrol backstop (see Self-healing: stranded completed epics): useful when no koryph run loop is currently active to self-heal it. Each finding is a warning naming koryph epic validate <id> as the recovery command.

JSON output

{
  "at": "2026-07-02T12:00:00Z",
  "home": "/Users/you/.koryph",
  "findings": [
    { "check": "layout", "level": "ok", "message": "layout ok" },
    { "check": "zombie-leases", "level": "ok", "message": "fixed",
      "fixed": true }
  ],
  "fixed_count": 1
}

In project mode the response includes a "project" field and "home" is the project's repo root:

{
  "at": "2026-07-02T12:00:00Z",
  "home": "/Users/you/src/myproject",
  "project": "myproject",
  "findings": [
    { "check": "project-config", "level": "ok", "message": "project_id=myproject work_source=bd" },
    { "check": "stalled-runs",   "level": "warn", "message": "stalled slot: run=20260702-100000 phase=bead-x status=running age=1h30m0s" }
  ]
}

The top-level fixed_count is non-zero only when --fix was passed and files were actually removed (global mode only).