Skip to content

henrique-marques-present/claude-hooks-sample

Repository files navigation

my_application_powered_by_hooks

A Flutter app used to showcase Claude Code hooks.

Hooks are shell commands that Claude Code runs automatically at specific points in its lifecycle (before/after a tool runs, when a session starts, etc.). They let you enforce policy and automate chores deterministically — without relying on the model to remember to do them. This repo wires up three small Python hooks so you can watch them fire during a normal Claude Code session.

The app itself is just the default Flutter counter template; the interesting part is the .claude/ directory.

What's wired up

Hooks are registered in .claude/settings.json, which maps a Claude Code event + tool matcher to a script:

Event Matcher Script What it does
PreToolUse Bash block_rm_rf.py Blocks dangerous rm -rf commands before they run.
PostToolUse Edit|Write|MultiEdit format_dart.py Runs dart format on a .dart file after Claude edits it.

Both scripts share hook_logger.py, which appends a timestamped line (and the full payload) to .claude/hooks/hooks.log every time a hook fires — handy for seeing exactly what Claude Code passed in.

How a hook receives and returns data

Claude Code pipes a JSON payload describing the tool call to the script's stdin. The script inspects it and communicates back via stdout/exit code:

  • Allow (the default) — print nothing and exit 0.
  • Block / deny — print a JSON decision on stdout. For PreToolUse, a permissionDecision: "deny" stops the tool from running and shows the reason to Claude.

The hooks

🛡️ block_rm_rf.py — PreToolUse safety guard

Inspects every Bash command before it executes. If the command is a recursive and force rm (e.g. rm -rf, rm -fr, rm --recursive --force), the hook returns a deny decision so Claude Code refuses to run it. Anything else is allowed through untouched. This is a deterministic guardrail — the model can't talk its way past it.

🎯 format_dart.py — PostToolUse auto-formatter

Runs after any Edit, Write, or MultiEdit. If the touched file ends in .dart, it shells out to dart format <file> so edits are always consistently formatted. Non-Dart files are skipped, and if dart isn't on PATH the hook silently no-ops — it never blocks or fails the turn.

🪵 hook_logger.py — shared logging helper

Not a hook itself; a small module imported by the other two. It exposes:

  • log(script, **props) — one timestamped key=value line per call.
  • log_payload(script, payload) — the full, untruncated stdin payload.

Output goes to .claude/hooks/hooks.log. Logging failures are caught so they can never break a safety or formatting hook.

Setup

The hooks are Python scripts with no third-party dependencies, so there's nothing to install beyond the basics:

  1. Python 3 on your PATH (the scripts run via python3).
  2. Dart/Flutter SDK so dart format is available for the formatter hook (optional — the hook no-ops without it).
  3. Open this project in Claude Code. Project hooks in .claude/settings.json are picked up automatically. Run /hooks inside Claude Code to confirm they're registered.

Security note: Hooks run arbitrary shell commands with your user permissions whenever their event fires. Review hook scripts before trusting a project's .claude/settings.json.

Try them out

  • Formatter: ask Claude to edit any .dart file (e.g. lib/main.dart) and watch dart format run afterward.

  • Safety guard: ask Claude to run rm -rf on something — the command is blocked before it executes.

  • Logs: tail the shared log to see each hook fire:

    tail -f .claude/hooks/hooks.log

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors