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

See also