Every failure this fleet actually hit was silent. 243 dangling symlinks were
invisible to git because **/.claude/skills/ is in the global gitignore. A
checkout 20 skills behind still resolved, so nothing complained. A loose
secret-hygiene.md loaded in no harness and errored in none. Two machines had
the library missing entirely. Exit status is the signal that was absent.
bin/doctor reports, with a non-zero exit:
- dangling links, anywhere under the harness dirs
- links that RESOLVE but land outside the active checkout — the dangerous
case, because it works while serving stale content
- skill-count mismatch between a harness and the checkout
- stray entries where <name>/SKILL.md was expected
- leftover per-skill Claude symlinks (plugin skills do not override
same-named user skills, so leftovers mean everything loads twice)
- divergence between the plugin copy and the copy the other harnesses link to
Wired as a SessionStart hook with --quiet, so a healthy host prints nothing and
a drifting one says so at the top of the next session. That beats a timer:
no launchd/systemd wiring, and it reaches every host that installs the plugin.
27ms on koala, no network, no git fetch.
Two design points found by the tests, both worth keeping:
- The canonical checkout is whatever ~/.agents/skills resolves to, NOT the
copy doctor runs from. As a plugin hook $0 lives in the plugin cache, which
is legitimately a second copy; judging harness links against it flagged
every one of them as "outside".
- "cd X/.." resolves ".." lexically against the link path and lands back in
~/.agents. Resolve the link first, then take the parent.
tests/doctor_test.sh drives seven fixtures through $HOME and $DOCTOR_CHECKOUT,
so no test touches the real host. Each asserts both the non-zero exit and that
the report names the fault — an exit-code-only assertion would pass while the
message said nothing useful.
Bump-Type: minor
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m
40 lines
804 B
JSON
40 lines
804 B
JSON
{
|
|
"hooks": {
|
|
"SessionStart": [
|
|
{
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/bin/doctor\" --quiet",
|
|
"timeout": 5,
|
|
"statusMessage": "Checking skills wiring..."
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"UserPromptSubmit": [
|
|
{
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/pre-task-ritual.sh\"",
|
|
"timeout": 5
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"PreToolUse": [
|
|
{
|
|
"matcher": "Bash",
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/secret-guard.sh\"",
|
|
"timeout": 5
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|