| name | doctor |
| description | Diagnose your learning-loop installation. Runs health checks, presents issues, offers per-fix remediation, re-runs each check after the fix to confirm. Safe to run anytime; only makes changes you approve. Pass --redact to scan plugin data files for leaked credentials instead. |
/learning-loop:doctor
A read-mostly diagnostic. Runs the health-check library, presents the result, and walks you through fixes one at a time.
If invoked with --redact, skip the normal health-check steps and run the Redact mode section instead.
Paths
Resolve PLUGIN_DATA, VAULT, and the plugin root per ${CLAUDE_PLUGIN_ROOT}/skills-shared/paths-preamble.md (read it and apply).
fix strings in the health-check JSON prefix paths with a user-facing PLUGIN shorthand for the plugin root (defined in guide/troubleshooting.md; the guide/ tree is not shipped with the plugin). Show them verbatim when the user will run the command; substitute ${CLAUDE_PLUGIN_ROOT} for the leading PLUGIN segment when you execute it yourself via Bash.
Step 1: Run all checks
node ${CLAUDE_PLUGIN_ROOT}/scripts/health-check.mjs --full --json
Parse the JSON. The schema:
{
"ts": "...",
"ran": "full",
"checks": [
{ "id", "name", "status": "ok" | "fail", "severity": "ok" | "warn" | "fail", "detail", "fix" }
]
}
Step 2: Present the report
Format the dashboard like this:
learning-loop doctor
====================
Health checks (N passed, M warnings, F issues):
โ Node.js v25.9.0
โ Claude Code 2.1.145
โ ~/.local/bin on PATH not on PATH
โ Add to your shell rc: export PATH="$HOME/.local/bin:$PATH"
โ ll-search binary missing at /Users/.../bin/ll-search
โ Run /learning-loop:init to re-download the binary
โฆ
F issues, M warnings.
Icon rules:
โ when status === "ok"
โ when status === "fail" && severity === "warn"
โ when status === "fail" && severity === "fail"
Step 3: If F + M === 0
Print โ All checks pass. Nothing to fix. and exit.
Step 4: Otherwise, iterate fails first, then warns
For each check with status === "fail":
- Show the check:
โ <name>: <detail>
Suggested fix: <fix>
- Ask via
AskUserQuestion:
- Option A:
Fix this (auto-runnable) โ only when the fix is in the auto-runnable table below
- Option A':
Run the suggested command and tell me when done โ when the fix is manual
- Option B:
Skip โ I'll handle this later
- Option C:
Stop the doctor session โ exits cleanly
- On choice A: execute the corresponding fix command via Bash. After it finishes, re-run
node ${CLAUDE_PLUGIN_ROOT}/scripts/health-check.mjs --full --json and find the same check by its id field. Report:
โ Fixed (new state: <detail>) if the check now returns ok
โ Still warning: <new detail> if it improved to warn
โ Still failing: <new detail> if it didn't help (don't loop โ move on)
- On choice A' (manual): print the command, then ask
Done? [Y]es / [N]o. On Yes, re-run node ${CLAUDE_PLUGIN_ROOT}/scripts/health-check.mjs --full --json and find the same check by its id field, then report as above.
- On choice B: track as skipped and move to next.
- On choice C: print summary and exit.
Auto-runnable fixes
| Check id | Action |
|---|
binary-exists, binary-version-file, binary-runs | node ${CLAUDE_PLUGIN_ROOT}/scripts/download-binary.mjs |
shims-exist | node ${CLAUDE_PLUGIN_ROOT}/scripts/install-shims.mjs --install |
vault-folders | mkdir -p each missing folder under <VAULT> |
vault-system-files | Write the default content defined in ${CLAUDE_PLUGIN_ROOT}/skills/init/phases/02-vault.md ยง2c |
search-index-exists | node ${CLAUDE_PLUGIN_ROOT}/scripts/vault-search.mjs index (resolves vault/db paths itself; bare ll-search index fails โ the binary requires explicit <VAULT_PATH> <DB_PATH> positionals) |
duplicate-gate-health (repeated timeouts) | ll-watch (start the warm daemon so the duplicate gate uses the socket instead of cold-starting the model on every write) |
duplicate-gate-health (stale daemon) | Kill the running ll-watch process then ll-watch (restarts with the new binary that supports duplicate-scan) |
injection-shadow-gate (ready for review) | Offer two options: Flip live (node -e 'const fs=require("node:fs");const p=process.argv[1];const c=JSON.parse(fs.readFileSync(p,"utf8"));c.injection_mode="live";delete c.injection_nudge;fs.writeFileSync(p,JSON.stringify(c,null,2)+"\n")' <PLUGIN_DATA>/config.json) or Hold in shadow (node -e 'const fs=require("node:fs");const p=process.argv[1];const c=JSON.parse(fs.readFileSync(p,"utf8"));c.injection_nudge="dismissed";fs.writeFileSync(p,JSON.stringify(c,null,2)+"\n")' <PLUGIN_DATA>/config.json). Always run node ${CLAUDE_PLUGIN_ROOT}/scripts/review-shadow.mjs first so the user can judge injection quality. The flip is consent-gated: never apply without the user picking the flip option. If <PLUGIN_DATA>/config.json is missing, stop and report instead of creating a partial config. |
watch-daemon-status (stale pidfile) | rm <pidfile> then ll-watch (no arguments โ that is the background-start invocation; start is not a subcommand) |
abi-drift | npm rebuild in the affected plugin directory |
Manual-only fixes (user must run; doctor reports the command):
episodic-memory-installed, learning-loop-installed โ claude plugin install <name>@<marketplace>
local-bin-on-path โ user adds to their shell rc
claudemd-section-present, claudemd-section-current โ defer to /learning-loop:init Phase 5
vault-path โ defer to /learning-loop:init Phase 2
plugin-cache-version-present โ claude plugin install โฆ
node-version, claude-version โ run install.sh or upgrade manually
Step 5: Summary
After all checks have been processed, print:
Doctor summary
==============
Fixed: F_fixed
Skipped: F_skipped
Manual: F_manual (commands above)
Now-warn: F_warn (started fail, now warn)
Then write the final result via:
node ${CLAUDE_PLUGIN_ROOT}/scripts/health-check.mjs --full --json > <PLUGIN_DATA>/last-health.json
(One last cache refresh so the next session-start detector reflects the post-doctor state.)
Rules
- Never make changes without explicit per-fix consent.
- Every fix gets verified by re-running the same check function. Never assume.
- If a fix command produces unexpected output (non-zero exit, error to stderr), surface it; don't pretend it succeeded.
- Use UTF-8 indicators (
โ, โ , โ, โ). No ASCII fallback.
- Exit code 0 even if some issues remain โ the doctor's job is to inform + offer, not gate.
Redact mode (--redact)
Scans persisted plugin-data text files for likely credentials. Only text files are scanned: .jsonl, .json, .md, .log, .txt. Binary databases (*.db, including edges.db and any federation index.db files) and other binary files are never read as text or rewritten โ skip them unconditionally.
Step 1: Locate plugin data
node ${CLAUDE_PLUGIN_ROOT}/scripts/resolve-paths.mjs
Note the PLUGIN_DATA path. Recursively collect files under that path, then filter to those whose extension is in the text allowlist (.jsonl, .json, .md, .log, .txt). Skip everything else, including any .db files.
Step 2: Scan for secrets
node ${CLAUDE_PLUGIN_ROOT}/scripts/redact-scan.mjs <text-files...>
Pass only the text-allowlisted files. The script will also skip any non-text file passed to it (printing a notice to stderr). For each file with hits, the script prints one line per finding:
<path>: <kind> <MASKED>
where <MASKED> shows the first 4 and last 2 characters with the middle replaced by * (e.g. ghp_****************************3z). The full secret is never printed.
Detection patterns:
| kind | matches |
|---|
github-pat | ghp_ followed by 30+ alphanumeric characters |
openai-key | sk- followed by 16+ alphanumeric characters |
slack-token | xox[baprs]- followed by 8+ alphanumeric or hyphen characters |
jwt | eyJ followed by 8+ base64url characters |
Detection is intentionally conservative โ the patterns target known credential prefixes to avoid alarm fatigue from false positives.
Step 3: Present findings
List each file with hits, showing kind and the masked match. Files with no hits are not listed.
If no hits are found across all files, print โ No credentials found in plugin data. and exit.
Step 4: Offer per-file scrub
For each text file with hits, ask via AskUserQuestion:
- Option A:
Scrub this file โ replace matching secrets with [REDACTED]
- Option B:
Skip this file
- Option C:
Stop
Binary files (.db, .wasm, extensionless binaries) are never offered for scrub โ they were excluded in Step 1. Scrub only runs on text-allowlisted files.
On choice A, replace each hit in the file content with [REDACTED] and write the file back. Re-run the scan on the file to confirm zero hits remain, then report:
โ Scrubbed <path> โ N secrets replaced
โ Still has hits after scrub: <path> (surface the remaining masked matches; do not loop)
Rules
- Never auto-scrub. Every file scrub requires explicit per-file consent.
- Never print a full secret โ always mask (first 4 + last 2 chars, rest
*).
- Never delete files. Scrub means in-place replacement with
[REDACTED].
- Exit code 0 even when hits are found โ this is a report, not a gate.
When to use
- After running
install.sh, to verify setup
- When session-start shows
โ learning-loop: N issues โ run /learning-loop:doctor
- Before opening a support issue ("paste me your doctor output")
- As a habitual health check after a Claude Code update or plugin reinstall
- Before sharing plugin data with a third party (
/learning-loop:doctor --redact first)