Structural patterns across languages
Match code structure — not text — so the same rule catches a real
panic()call in Go, TypeScript, Rust, or Python without flagging the word in a string or comment.
A regex on panic( flags the call inside strings and comments too, and StyleRule matchers only understand Python’s AST. The Pattern condition matches structure through ast-grep, so panic($$$) catches a real call however its arguments are spelled, and never a string that happens to contain the word.
"""Warn on Go panic() calls so callers return errors instead of aborting the process."""
from __future__ import annotations
from captain_hook import Allow, Event, Input, Pattern, SourceEdits, Warn, hook
hook(
Event.PostToolUse,
only_if=[SourceEdits(lang="go"), Pattern("panic($$$)")],
message="panic() aborts the whole process. Return an error so callers can recover; "
"reserve panic for truly unrecoverable programmer mistakes.",
tests={
Input(tool="Edit", file="server/handler.go", content='func h() {\n\tpanic("boom")\n}\n'): Warn(pattern="error"),
Input(tool="Edit", file="server/handler.go", content="func h() error {\n\treturn err\n}\n"): Allow(),
},
)What it catches
func h() {
panic("boom") // panic() aborts the process; return an error instead
}What it allows
func h() error {
return err // returns an error for the caller to recover from
}The catch / allow split mirrors the hook inline tests, so it stays true as the hook evolves.
Run it yourself
uvx capt-hook --hooks docs/examples test