Skip to content
Back to skills

Doctor

BSecurity

Health-check AND repair anti-hall — confirm Node is found, every hook is present + syntax-valid, the guards actually fire (live behavioral self-tests on git-guard / command-guard / edit-guard / swarm-guard / model-routing-guard), the statusline is installed, and (with --repair) apply safe fixes. Plain doctor is read-only. Use when the user asks "is anti-hall working / active / running", "check the hooks", "anti-hall doctor", "repair anti-hall", "fix the daemon", "are the guards on", or after ...

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added October 2, 2026
ai-agentsrustgobashsqlnodegitbackend

Works with

  • mcp

Security analysis

B75/100
  • criticalModifies startup scripts or system services for persistence

Pro shows the line behind each finding and how to fix it

Scanned October 3, 2026

npx -y skills add talas9/anti-hall --skill doctor --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Doctor?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Doctor
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/talas9-doctor/badge)](https://www.skillsdirectory.com/skills/talas9-doctor)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: doctor
description: Health-check AND repair anti-hall — confirm Node is found, every hook is present + syntax-valid, the guards actually fire (live behavioral self-tests on git-guard / command-guard / edit-guard / swarm-guard / model-routing-guard), the statusline is installed, and (with --repair) apply safe fixes. Plain doctor is read-only. Use when the user asks "is anti-hall working / active / running", "check the hooks", "anti-hall doctor", "repair anti-hall", "fix the daemon", "are the guards on", or after install/update to verify everything is live.
---

# Doctor

Answers the only question that matters for a guardrail plugin: **is it actually running,
and do the guards actually fire?** It checks presence AND behavior — not just that files
exist, but that the guards block what they should and allow what they should. It can also
**repair**: `doctor --repair` diagnoses everything AND applies the safe fixes (see
[Repair mode](#repair-mode) below). A plain `doctor` is **read-only** — it reports and
changes nothing.

## What it reports
- **Environment:** Node version (and whether it's ≥ 18 — below that the hooks silently
  no-op), platform, plugin version.
- **Hooks:** every script registered in `hooks.json` is present and syntax-valid.
- **Guard behavior (live self-tests):** spawns the real guards with crafted payloads and
  asserts exit codes — git-guard blocks force-push + AI self-credit and allows `git status`;
  command-guard blocks heavy commands in the coordinator but ALLOWS them in a subagent
  (payload `agent_id`); edit-guard blocks a coordinator Edit but ALLOWS the same Edit in
  a subagent (payload `agent_id`/`agent_type`), sharing that coordinator-vs-subagent
  discriminator with command-guard via `coordinator-detect.js`; swarm-guard allows a
  normal spawn under healthy memory; model-routing-guard blocks a mechanical task pinned
  to a flagship model (synthetic payload: fetch/download description + `model:"opus"` +
  `subagent_type:"general-purpose"` → exit 2) and allows a benign spawn with no model and
  no mechanical signals (exit 0); omc-detect.js (the OMC-deference shared helper consumed
  by task-guard / tasklist-guard) is checked for presence + syntax validity.
- **Phantom Primary rows:** report-only detection of Primary registrations keyed by a submodule cwd; only `--repair` archives them (nothing is deleted).
- **Handover format:** warns (report-only, nothing moved) about handover files tracked by git or sitting outside `.anti-hall/handovers/<date>/<session_id>/`, each with its canonical destination.
- **Statusline:** whether a statusLine is installed and in which scope.
- **DevSwarm RUNTIME health** (when the DevSwarm gate is active — same gate as the
  liveness supervisor section): store/journal health (sqlite `quick_check` via an
  isolated `--no-warnings` read-only probe, journal torn-line scan, store↔summary
  parity), data staleness (summary.json age, gated on the daemon actually running
  AND a workspace having unread backlog — an idle system never false-alarms),
  daemons **RUNNING** vs merely installed (per-worktree ingest + the supervisor —
  distinguishes installed-but-DEAD from not-installed), and a no-other-consumer scan
  (a second native `hivecontrol workspace monitor` process would split the
  destructive queue — report-only, never killed). See [Runtime health
  checks](#runtime-health-checks) below.
- **Orphaned workspace processes** (`companion/lib/doctor-devswarm.js`'s
  `orphanedWorkspaceProcessCheck`): a bounded (≤2s), report-only scan for any
  LIVE process whose **cwd** sits inside a workspace worktree that is now
  archived (anti-hall's own `archived/<id>.json`, or the DevSwarm app DB's own
  archived builders) or simply gone (an active descriptor whose worktreePath no
  longer exists on disk). Prints `N process(es) alive with cwd in archived/gone
  workspaces: pid <pid> (<comm>) cwd=<path> [<reason>]; ...` — pid, command name,
  and cwd for each — and a suggested MANUAL `kill <pid>` command. **Never kills,
  signals, or closes anything itself.** macOS reads `lsof -a -d cwd -Fpcn`;
  Linux walks `/proc/<pid>/cwd`. Silent when nothing is stale to watch for or
  the scan finds no hit.
- **Foreign skill/hook conflict scan** (always runs, independent of DevSwarm):
  cross-references other ENABLED plugins' `hooks.json`/skills against anti-hall's
  own. See [below](#foreign-skillhook-conflict-scan).
- A final verdict: `anti-hall ACTIVE — N checks passed`, or the list of failures.

## Runtime health checks

Wired into the existing "DevSwarm liveness supervisor" section (same gate:
`isDevswarmActive(env)` or a published workspace descriptor), implemented in
`companion/lib/doctor-runtime.js`. All four are **report-only** — none of them
restart, reinstall, or kill anything (that stays doctor-repair.js's job, still
gated the same way):

1. **DB/store health** — ENUMERATES every PER-PROJECT store (`store/<hash>/devswarm.db`,
   plus a legacy flat `store/devswarm.db` if a pre-migration one is present) and, for
   each, opens a SEPARATE **read-only** `node:sqlite` handle and runs `PRAGMA
   quick_check`. WAL means this can never
   block the ingest daemon (its single-consumer lock is a distinct O_EXCL
   lockfile, not a DB lock). The `require('node:sqlite')` call happens **only**
   inside a `--no-warnings` child probe, so its ExperimentalWarning never reaches
   doctor's own stderr. The journal backend gets a torn-line scan (a torn line
   followed by more valid content is real corruption; a torn *trailing* line is a
   normal in-flight write and is never flagged). Store↔summary parity flags
   **only** `summary.total > store.total` (a store ahead of a not-yet-derived
   summary is normal and never flagged).
2. **Data staleness** — flags a stale `summary.json` only when the daemon is
   confirmed RUNNING (check 3) *and* a workspace has `unread > 0`; prefers the
   ingest daemon's own per-worktree heartbeat (`heartbeats/ingest-<hash>.json`,
   rewritten every sweep) over the blunter `generatedAt`, which only advances on
   an actual insert.
3. **Daemons RUNNING, not just installed** — per-worktree ingest units (via
   `listInstalledIngestUnits`) and the supervisor: `launchctl list` / `systemctl
   --user is-active` / (cron fallback) heartbeat-or-`ps`. Distinguishes
   installed-but-DEAD (WARN) from not-installed (INFO, not a warning).
   **v0.66 — alive but ingesting nothing:** a daemon can be RUNNING (fresh
   heartbeat, live-pid lock) while every `hivecontrol workspace monitor` spawn
   it makes still FAILS (a permanent config fault, e.g. ENOENT/EACCES/ENOTDIR
   — commonly a bare binary name plus the scheduler's minimal `PATH`). The
   heartbeat now carries the monitor OUTCOME (`consecutiveMonitorFailures` /
   `lastMonitorOkMs` / `lastMonitorErrorCode` — see `devswarm-ingest.js`'s
   `writeIngestHeartbeat`), and repair mode reports this as its own **FAILURE**
   (not "installed and healthy") once 3+ consecutive failures are recorded, or
   no monitor poll has succeeded for longer than `devswarm.monitorNoOkFailMin`
   (default 10 min) — since the last success, or since the daemon started
   (`startedAtMs`) when it has never succeeded; inside that window a fresh
   daemon reports **starting up**, not failing. The heartbeat also carries
   `lastMonitorAttemptMs` and `lastMonitorError`. **The remedy depends on
   WHICH kind of fault it is** (`hooks/lib/doctor-repair.js`'s exported
   `isMonitorConfigFault()`): a genuine config fault — the daemon's own
   monitor breaker only stamps `lastMonitorErrorCode` for a PERMANENT
   spawn-error code (`ENOENT`/`EACCES`/`ENOTDIR` — the binary itself cannot be
   resolved/executed) — is repaired by reinstalling, which bakes the resolved
   absolute binary + `PATH` into the regenerated unit. A **slow or
   timing-out** `hivecontrol` (`ETIMEDOUT`, or any failure without a
   resolvable spawn code — `lastMonitorErrorCode` stays `null`) is reported as
   its own plain-language **status** (`monitorSlowReason()`) and is **never
   reinstalled or restarted**: the daemon process is alive and its heartbeat
   is fresh, so a reinstall would only interrupt an otherwise-healthy daemon
   without making `hivecontrol` respond any faster. A heartbeat missing these
   fields (an older daemon build that has not been relaunched yet) is
   UNKNOWN, never a fault. The SAME verdict (same thresholds, same
   missing-fields=UNKNOWN rule — one shared predicate, `hooks/lib/
   doctor-repair.js`'s exported `monitorFaultFor()`) also drives the
   **in-session hot-path banner**: `companion/lib/ingest-health.js`'s
   `daemonHealth()` returns `status:'failed'` (distinct from `'healthy'` and
   `'stale'`) for either kind of monitor fault, and `buildMonitorFaultBanner()`
   renders the one-line in-session warning (shown once per fault episode, not
   on every prompt). For a config fault it says to run doctor; for a timeout it
   says the daemon is healthy, the DevSwarm app's `hivecontrol` is not
   answering, mesh messages are unaffected, and doctor cannot repair it — check
   or restart the DevSwarm app — so a daemon that is
   alive-but-broken (of either kind) is never misreported as a mere staleness
   blip, and repair mode alone decides whether that means reinstall or just a
   status line.
4. **No other consumer** — reads the per-worktree ingest lock(s) and cross-checks
   against a `ps` scan for `hivecontrol workspace monitor` processes; more than
   one, or one holding no lock, is a high-severity WARN (report-only — the
   single-consumer invariant is never enforced by killing anything here).

## Foreign skill/hook conflict scan

Its own **unconditional** top-level section (`companion/lib/doctor-runtime.js`'s
`scanForeignConflicts`) — runs regardless of DevSwarm state. Cross-references
every OTHER **enabled** plugin's `hooks.json` (found via
`~/.claude/plugins/installed_plugins.json`, the harness-owned install index) and
skill directory names against anti-hall's own. A foreign `PreToolUse` hook on
Bash or a second `Stop` hook is surfaced (WARN — third-party plugin config is
never a reason to fail anti-hall's own exit code); additive `UserPromptSubmit`/
`SessionStart` overlap is INFO (non-competing); a skill-name collision is WARN.
**Privacy:** only the plugin name, event, matcher, and hook script **basename**
are ever reported — never a full command string (which can carry a local
username/path) or file contents.

## Repair mode

Plain `doctor` is **read-only**: full diagnosis, no repair pass, nothing changed. Repairs
are **opt-in**: `doctor --repair` (alias `--fix`) runs the diagnosis AND then a **repair pass**
that fixes what it safely can. The repair pass writes files/units, so the same
Haiku-delegation rule applies (below). When the user asks to "repair" / "fix" anti-hall,
pass `--repair`; for "is it working?" run it plain.

Flags:

| Invocation | Behavior |
|---|---|
| `node hooks/doctor.js` (no flags) | FULL detection, **read-only** — no repair pass. This is the default. |
| `--repair` / `--fix` | FULL detection + apply AUTO-SAFE fixes + GATED daemon fixes only when the DevSwarm gate is open. |
| `--dry-run` | Detection + print exactly what WOULD be fixed. **Writes nothing** (threads each installer's own `--dry-run`, migrate-state `dryRun:true`). |
| `--check` | **PURE read-only** — detects + reports everything, mutates NOTHING. The CI / scripting path. |
| `--quiet` | One-line verdict only (combines with any of the above). |

**Two safety classes:**

- **AUTO-SAFE** (always applied, honors `--dry-run`): legacy/GSD/DevSwarm-store state
  migration; statusline install **only when NO statusLine is configured in any scope** (a
  custom statusLine is never overridden); idempotent relaunch of an ALREADY-installed
  supervisor; refresh of Codex hooks when a `.codex/config.toml` exists but the hooks are
  unwired (it never creates a new `.codex`).
- **GATED** — applied only when the **DevSwarm gate** is open: `isDevswarmActive(env)` (a
  DevSwarm-active session) **AND** `resolveWorktree(cwd)` is a real git worktree. Covers
  the ingest daemon install, the **v0.54.1 wrong-path ingest heal** (a unit whose
  `WorkingDirectory` no longer points inside a worktree is rebuilt from the right one),
  stale ExecStart script, the supervisor FIRST-install, and (**v0.58.1**) **`reconcile`**
  — draining every stranded per-worktree native hivecontrol queue into the shared store
  (`node scripts/devswarm.js reconcile`, previously a MANUAL-only verb). Unlike the
  daemon fixes, `reconcile` never touches launchd/systemd, so it needs no scheduler.
  When the gate is closed, doctor **reports the gap plus the exact manual command** and
  mutates nothing.
- **REPORT-ONLY:** the MCP orphan reaper is never auto-installed (it kills orphans on a
  timer) — doctor only prints how to enable it.

**One migration registry.** The all-store DevSwarm forward-migrations (fold-all-stores,
heal-orphan-partitions, fold-archived-rows, fold-archived-family-descriptors,
reconcile-dual-partition-acks) are listed once in `companion/lib/migrations.js` and shared by `doctor --repair`, `update` and the
supervisor. Each is stamped done per plugin version in `~/.anti-hall/update-sweep-state.json`
after one clean pass, so a repeat `--repair` skips it with a single marker read instead of
re-scanning every store. Deletion-class repairs (`--repair-resurrected`) are never in that
set — opting into `--repair` is not opting into row removal. A twin descriptor the
`fold-archived-family-descriptors` pass deliberately refuses to retire (its worktree still
exists, or an archived copy already exists and differs) is not a failure: the row is
`skipped`, lists each twin (8-char id + reason, first 10 then `+N more`) and says it was left
untouched on purpose — check those two things if you want to resolve it by hand.

**Plugin cache prune (opt-in, never automatic).** `doctor --prune-cache` lists the old
`~/.claude/plugins/cache/anti-hall/anti-hall/<semver>/` dirs it would remove and their total
size; only `doctor --prune-cache --confirmed` removes them, logging each removal. It keeps the
newest 3, the `installPath` registered in `installed_plugins.json`, every version a live
process runs from (cwd or argv), the running version, and anything it cannot parse; symlinks
and paths outside that root are refused, and nothing is removed if the process scan is
unavailable. Nothing else ever runs it (not `update`, the supervisor, a cron or a hook).
Setting `updates.allowCachePrune` (default `true`) enables the verb.

**Leaked scheduler units (always on, report-only).** Every run (including plain `doctor`
and `--check`) scans each anti-hall launchd/systemd unit file — ingest, supervisor, reaper —
and flags one whose `WorkingDirectory` is under a temp root or gone, or whose script is
gone. Nothing is unloaded or moved; the report prints the exact bootout + quarantine
(rename, never delete) commands for a human to run.

After each fix, doctor **re-runs the relevant detection** to confirm it actually took
before reporting `FIXED` (a spawned installer's exit code is not trusted — `launchctl load`
can warn). A `FAILED` repair keeps the exit code non-zero.

**Complementarity with `update` (SKILL step 7):** both `doctor` and the `update` skill call
the same idempotent installers under the same DevSwarm gate, so running both is harmless —
a double-refresh is a no-op. `update` refreshes on version change; `doctor` repairs on
demand.

Windows is not supported.

## How to run

The doctor is a `node` script that now **writes units/settings** in repair mode (and the
command-guard blocks heavy commands on the main thread), so **delegate it to a Haiku
subagent** (`model:"haiku"` — an execution-shaped spawn with no explicit model also trips
model-routing-guard's strict-mode block) and relay the report:

```
node "${CLAUDE_PLUGIN_ROOT}/hooks/doctor.js"           # diagnose only (default, read-only)
node "${CLAUDE_PLUGIN_ROOT}/hooks/doctor.js" --repair  # diagnose + apply the safe repairs
node "${CLAUDE_PLUGIN_ROOT}/hooks/doctor.js" --dry-run # show what --repair would fix
node "${CLAUDE_PLUGIN_ROOT}/hooks/doctor.js" --check   # read-only (CI)
```

Add `--quiet` for just the one-line verdict. Exit code is non-zero if any critical check
(or repair) fails, so it is scriptable in CI too — use `--check` there to keep it read-only.

After relaying the report, if anything failed: the most common fix is **Node missing/<22**
(install Node ≥ 22) or **no statusLine** (repair mode installs it automatically, or run the
`install-statusline` skill, then restart).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.