Unified health check — cyberine doctor (T540)
cyberine doctor # everything below, one report, offline-safe
cyberine doctor --liveness # is each adapter FUNDED, not just keyed (see below)One report covering the whole install, printed as pass/warn/fail lines with a fix hint on every warn/fail:
Only actionable lines are [WARN] (kbxeg). Configuration reports print as [INFO] and never affect Overall: a seat whose token file exists with mode 600, the seat routing policy, the seat rotation pointer and the step routing policy. Providers without a key are one [INFO] no key for N provider(s): ... line instead of one [WARN] per provider; the N/M providers configured line still warns at zero, and a totally unconfigured install is still the [FAIL] under Dispatchability. When the Claude Code plugin is absent, the missing bundled skills are one [WARN] naming cyberine plugin install. Measured on the author's machine 2026-09-28: 36 [WARN] lines became 10. Known Limitations and Deferred Work: the RETIRED-worker lines and the registry counts still print as [WARN]; cyberine fleet doctor prints the same info lines under its own INFO: heading.
-
Runtime — Node version vs the documented minimum (20+).
-
Config files —
config.json(global/project/local) andhooks.json(global/project) JSON validity, distinguishing "missing" (fine) from "malformed" (flagged); whichfleet.jsonis in effect. -
Keybindings —
loadUserBindings()warnings (unknown action ids, malformed file). -
Cyberine home —
$CYBERINE_HOMEwritability. -
Run store — sanity of
$CYBERINE_HOME/runs/*.json(the cross-project registrycyberine agentsreads): corrupt record files, and records whose project directory no longer exists on disk. Also (T674) opens each record's own<runsDir>/<runId>/run.jsonand flags, distinctly from corrupt registry records, runs whose run.json is corrupt, unparseable, or structurally invalid (see below), and live records whose run dir has no run.json at all (an orphaned record); tombstoned (deleted) runs are skipped. The same corrupted-run count also renders live in the fleet TUI header as acorruptstat (local mode) instead of only a stderr line the alt-screen buffer swallows. Runnpm run selftest:doctor-run-corruptionto re-run the truncation drill headlessly. Known limitation: thecorruptheader stat covers local mode only (cyberine agents' global view and the web dashboard read the registry, notRunStore.list()— for those,cyberine doctoris the rendered channel), and the corrupted-run count is not exposed on/metricsyet.What "corrupt" means (kt7bx, 2026-08-27): a run.json is corrupt if it fails to read/parse (a torn write) OR if it parses as valid JSON but is not a structurally valid
Run—describeInvalidRun()(src/store/runTypes.ts), shared byRunStore.readRun()(backinglist()/get()/corruptedRunIds()) andcyberine doctor'scheckStoreSanity(), so the two surfaces never disagree. Before this,JSON.parsealone was treated as validation:{}, or a record missing a field every consumer dereferences unguarded, came backkind: 'ok'— a "healthy" run that then threw downstream at whatever field a caller read first (a fleet-TUI row renderingundefinedas its run id,cyberine cost/agents/digestcrashing onrun.steps). The check requires exactly the fields realRunconsumers read without a??/?.guard —runId,workflowName,state(one of the fiveRunStatevalues),detail,createdAt,updatedAt,cwd(all plain strings/numbers), andsteps(an array) — derived by grepping everyrun.<field>access acrosssrc/, not from theRuntype declaration alone. Every otherRunfield is optional in the type and read that way by its callers (run.lineage?.,run.paused ? ...), so it is deliberately NOT required here: failing a run over an absent optional field would be worse than the bug this closes. Verified against every run.json under this project's own.local/runs/(273 real records at the time) before shipping: all 273 already carried the eight fields with the expected types, so the guard does not retroactively mark any of the operator's own run history corrupt.Known Limitations and Deferred Work: the check is shallow — it validates the top-level
Runfields only, not the nestedRunStep/WorkerRunshapes (astepsarray whose elements are themselves malformed still passes), and it does not check thatstatetransitions make sense (e.g.donewith an emptystepsarray). It also does not validate any optional field's type when present (a wrong-typedlineageorbudgetis not caught). Full schema validation (Zod or similar) covering the wholeRun/RunStep/WorkerRuntree is separate, unfiled work.Note (2026-08-26):
cyberine doctorwas always the CORRECT channel here — it already separated "run.json is corrupt/unparseable" from "run dir has no run.json at all".RunStore.list()did not: it collapsed both ontoget()returningnulland reported every one as corruption. On a real tree that meant 485 of 758 directories, because.local/runs/is shared —LocalBrain.carry()writes capsule-only dirs (<runId>/context.md, no run.json beside it) into the same parent — and the warning interpolated the FULL joined id set, so everycyberineinvocation that toucheslist()(the fleet TUI,cyberine cost,cyberine eval, registry backfill) dumped a single ~18KB stderr line.list()now skips a missing run.json silently and only counts a real read/parse failure, and the warning names a sample plus a remainder count instead of every id. A genuinely malformed run.json is still reported on stderr AND still surfaces throughcorruptedRunIds()— verify both halves withnpm run selftest:runstore-corruption-scope. Measured on the tree that surfaced it: 758 directories, 273 with a parseablerun.json, 485 without one (481 capsule-only, plus 3 holding onlyconsole.logand 1 onlycontrol.json.done), and ZERO genuinely unparseable — so every id in that 18KB line was a false positive.Two things the audit corrected about this file's own older comments, worth not re-learning: the web
/api/runsroute is NOT aRunStore.list()consumer (it reads the global registry vialistRuns()), and the global fleet view loads records one at a time throughget(), so a corrupt run vanishes from THAT view with only a per-idconsole.error—cyberine doctorremains the durable diagnostic surface for registered runs.Known limitation / deferred work: the capsule directories deliberately stay under
.local/runs/—cyberine loop --resume .local/runs/<id>/context.mdreferences that path, so a.local/capsules/namespace would be a migration across writer, reader, docs, and existing artifacts. (kt7bx, 2026-08-27, closed the sibling gap that used to live here:JSON.parse-only validation accepting a structurally wrong run.json as healthy — see "What 'corrupt' means" above for the fix and its own remaining limits.) -
Providers — per-provider configured/missing (reuses
isConfigured()+ thePROVIDERSregistry; never prints key values). -
Spend & cap (T636) — daily-cap spent/remaining/resets, hard-cap spent/remaining (when
CYBERINE_SPEND_CAP_USDis set), and any credit-ledger entry at low/exhausted status. See "Spend visibility" under "Hard spend cap". -
Gateway — reachability probe (1.5s timeout) against the LiteLLM gateway, but ONLY when one is actually configured (opt-in signal); skipped entirely by default, so the whole report stays offline-safe.
-
Build-fleet CLIs — install + login state (
src/core/cliAvailability.ts, same signalcyberine fleet list's KEY column uses). -
Fleet policy (fleet.json) — reuses
cyberine fleet doctor's exact checker (structural errors, semantic warnings, drift) so the two commands never disagree. -
MCP servers — whether the
cyberine-mcpbinary (the fallback entry point, for a host with no plugin support; the plugin ships its own atplugin/.mcp.json) resolves on PATH, plus any externally-configured MCP servers (CYBERINE_MCP_SERVERS/CYBERINE_MCP_CONFIG) for shape + PATH-resolvable stdio commands — never actually connects. -
claude-code:— Claude Code wiring: the plugin symlink status (missingwith acyberine plugin installfix hint, orloaded), one status per bundled skill (when the plugin is present), whether the bundled MCP server is accessible, and theclaudeCLI's own version (or a not-on-PATH warning). Shares its checks withclaudeCodeWired()(src/core/claudeCodeWiring.ts) so this section and the barecyberinenudge (see "Smart barecyberine" above) never disagree on what "wired" means. -
Hooks executability — for any
hooks.jsoncommand that looks like a local script path (./foo.sh,/abs/path,~/bin/foo), checks the file exists and is+x. -
Repo readiness (kqyke) — for the git repo the command runs in: whether
.gitignorecovers.local/(probed asgit check-ignore -q .local/), whether any.local/content is already tracked, whether.cyberine/config.jsonexists and recordsrules.applied(the preconditioncyberine autopilotrefuses on), and whether the task board exists and holds at least one task. Every gap is a[WARN]with its fix; outside a git repo the section is one[WARN]saying readiness was not checked. Added because a session asked "is this repo ready for Cyberine" readOverall: OKfrom this report on a repo with an unignored.local/, no applied rules and an empty board (.local/research/plugin-eval/2026-09-19-eval-residuals-verdicts.m3max.md).Known Limitations and Deferred Work: the section never FAILs, so
Overalland the exit code do not move on a readiness gap, and it does not repeat the rest of the/cyberine:initprobe (node, theclaudeCLI, provider auth, the handoff writer, a rules conflict check) —/cyberine:initstays the full readiness pass and the only one that fixes anything.
Exit code reflects failures: 0 when every check is pass/warn, 1 when at least one [FAIL] line was produced (a malformed config file, an unwritable $CYBERINE_HOME, or a structurally-broken fleet.json). Warnings (missing provider key, unreachable gateway, fleet drift, stale registry entries, a non-executable hook script) never fail the exit code — they are things to notice, not things blocking a healthy install. The chat /doctor command prints the identical report inline. Run npm run selftest:doctor for the headless smoke test (seeds a broken config + a key-free environment, no TTY needed).
Fleet policy file — cyberine fleet (T422, T425, T426)
fleet.json is an optional provider policy file that declares which workers are active and which model handles each task kind. It externalizes the routing that is otherwise hardcoded across worker...
Adapter liveness — is it FUNDED, not just keyed (--liveness, kwrp9)
cyberine doctor answers "is a key present". It never answered "is that key's account funded", and neither did the fleet TUI's overview, which listed an adapter whenever its key existed and printed...