Claude-skill-registry hook-intercept-block
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/hook-intercept-block" ~/.claude/skills/majiayu000-claude-skill-registry-hook-intercept-block && rm -rf "$T"
manifest:
skills/data/hook-intercept-block/SKILL.mdsource content
Hook-Intercept-Block Pattern
Pattern for implementing slash commands that execute entirely in the hook handler, bypassing the Claude API call entirely.
Why Use This Pattern
- No API cost: Commands execute in Go, no Claude API call
- Faster: Direct execution vs markdown parsing + API round trip
- Deterministic: No model variance - same input, same output
How It Works
User types: /bumper-reset ↓ UserPromptSubmit hook fires ↓ prompt_handler.go matches regex: ^/(?:claude-bumper-lanes:)?bumper-reset\s*$ ↓ handleReset() executes Go logic ↓ Returns JSON to stdout: {"decision":"block","reason":"Baseline reset. Score: 0/400"} ↓ Claude Code shows "reason" to user, skips API call
The Confusing Naming
Claude Code's hook response API uses counterintuitive terminology:
| Response | What It Actually Means |
|---|---|
| "I handled this, don't call Claude API" (NOT "blocked/rejected") |
| "Let it through to Claude API" |
| Message shown to user (only with "block") |
Key insight:
block = "handled and done", not "rejected". The command succeeded.
Implementation Components
- Hook config (
): Routes UserPromptSubmit to handler binaryhooks.json - Handler (
): Regex matching + dispatchinternal/hooks/prompt_handler.go - Command stubs (
): MUST exist forcommands/*.md
discovery (body ignored)/help
Adding a New Command
Step 1: Add Regex Pattern
In
prompt_handler.go:
var newCmdPattern = regexp.MustCompile(`^/(?:claude-bumper-lanes:)?bumper-foo\s*(.*)$`)
The
(?:claude-bumper-lanes:)? makes the plugin namespace optional.
Step 2: Add Dispatch
In
HandlePrompt():
if m := newCmdPattern.FindStringSubmatch(prompt); m != nil { return handleFoo(sessionID, strings.TrimSpace(m[1])) }
Step 3: Implement Handler
Use the helper functions for DRY session management:
func handleFoo(sessionID, args string) int { sess := loadSessionOrBlock(sessionID) if sess == nil { return 0 } // ... your logic here ... if !saveOrBlock(sess) { return 0 } blockPrompt("Success message") return 0 }
Step 4: Create Command Stub
Create
commands/bumper-foo.md:
--- description: Does the foo thing argument-hint: <optional-args> --- This command is handled by the hook system.
The markdown body is ignored - the hook handles everything. The file MUST exist for the command to appear in
/help.
Step 5: Rebuild
just build-bumper-lanes
Helper Functions
Two helpers reduce boilerplate:
loadSessionOrBlock
func loadSessionOrBlock(sessionID string) *state.SessionState
Returns session state or nil. If nil, error already shown to user via
blockPrompt().
saveOrBlock
func saveOrBlock(sess *state.SessionState) bool
Returns true on success. If false, error already shown to user via
blockPrompt().
JSON Response Format
The
UserPromptResponse struct:
type UserPromptResponse struct { Decision string `json:"decision,omitempty"` Reason string `json:"reason,omitempty"` }
Output via
blockPrompt():
func blockPrompt(reason string) { resp := UserPromptResponse{ Decision: "block", Reason: reason, } out, _ := json.Marshal(resp) fmt.Println(string(out)) }
Existing Commands Using This Pattern
All bumper-lanes slash commands use hook-intercept-block:
| Command | Handler | Purpose |
|---|---|---|
| | Capture new baseline, reset score |
| | Disable enforcement |
| | Re-enable enforcement |
| | Set/show visualization mode |
| | Show/set threshold |
Debugging Tips
- Command not recognized: Check regex pattern matches user input exactly
- No output shown: Ensure
is called and JSON printed to stdoutblockPrompt() - Command not in /help: Verify
stub file existscommands/*.md - Binary not updated: Run
after changesjust build-bumper-lanes