overview / hooks
rabadon init merges these into .claude/settings.json. Each is a subprocess Claude Code invokes at a defined moment.
PreToolUse — before any tool runs. Carries the tool name and its input (for Bash, the command; for edits, the file path). This is where a forbidden action is refused before it happens. Matches every tool — Bash, file edits, and MCP tools alike.PostToolUse — after a tool runs and succeeds. Carries the tool name, input, and result. This is where a real check can catch a break the moment it lands.PostToolUseFailure — after a tool runs and fails. Claude Code delivers a failed tool call under this separate name and does not also fire PostToolUse for it, so subscribing to PostToolUse alone leaves a supervisor blind to every command that exited non-zero. rabadon treats it as the completion it is: the move is closed, the error signature is taken from the event's error field, and the closing ledger entry carries "rc":1 so a reader can tell a failure from a success. Measured on 2026-08-29; before that date rabadon did not subscribe to it and recorded nothing for a failing call.SessionStart — at the start of a session. Where rabadon prunes the spool past its retention window, seeds session state, and brings its own subscriptions up to date (below). It also states its terms. On Claude Code the hook answers with one JSON object: the full report (check, when, repair, scope, blind spots) is the systemMessage, which Claude Code shows to you and does not give the model; additionalContext, which the model does get, carries the last session's handoff and a short brief: the session is supervised, the check, a refusal names its rule. The blind-spot list never reaches the agent. Cursor and rabadon run print the report as plain text.UserPromptSubmit — when you submit a prompt. Carries the prompt; feeds session-goal and drift tracking.Stop — when the session ends. Fires the gate and, separately, the drift check.Claude Code hooks are one binding, not the product. rabadon's gate is a generic subprocess contract; any runtime that can run a subprocess before or after an action can implement it.
A hook subscription is written once, at install time. That makes "the binary is new, the subscription is old" a silent and permanent state, and on 2026-08-29 it was one: rabadon had learned PostToolUseFailure that morning, this machine's ~/.claude/settings.json had been written three days earlier, rabadon doctor reported "all green", and every command that exited non-zero was invisible. Shipped is not installed.
So the gate carries the upgrade. At SessionStart — the one event every install that has ever existed subscribes to — it compares what is registered against what the installer would write today, and re-registers the difference by calling the same installer rabadon init calls. Specifically:
.claude/settings.json and the user settings file Claude Code reads ($CLAUDE_CONFIG_DIR/settings.json when that is set, else ~/.claude/settings.json). With CLAUDE_CONFIG_DIR set, ~/.claude/settings.json is not loaded by Claude and is left alone;settings.json that already contains rabadon entries. Self-healing is not self-installing: a project you chose not to guard stays unguarded, and a machine you uninstalled from stays uninstalled;settings.json.bak-rabadon first, and hooks that are not rabadon's — along with your statusLine and every other key — are left exactly as they were;HOOKS_REFRESHED entry to the ledger. It does not claim the session is covered: your agent read its hooks before the gate ran, so the new events arrive from your next session on, and the card keeps reporting the blind spot for the current one;RABADON_SELFHEAL=0 turns it off entirely;rabadon doctor reports a stale subscription set as a problem and names the missing events, so the one-step repair (rabadon init --global) is still there if you would rather do it yourself.