Claude Code Formatter Hooks — 7 Auto-Format Templates | AI Code Toolkit
7 tested hooks · Install-once category · PostToolUse Write/Edit

Formatter Hooks

PostToolUse hooks that keep Claude Code's output formatted correctly — automatically, silently, and using the same config your team already committed. The single highest install-to-value category in the hooks library.

7
Hooks
4
Language Families
Silent
On Success
48h
Update SLA

Why formatter hooks are the highest install-to-value ratio in the whole hooks library

Formatter hooks are the "install once, never think about it again" category. You add three lines to settings.json, and from that moment on every file Claude Code writes or edits comes out formatted correctly — without you asking, without slowing the interaction down, without leaving any drift for humans to clean up later. Total cognitive overhead after installation: zero.

The 7 hooks on this page cover the mainstream formatters for JavaScript/TypeScript (Prettier, ESLint, tsc), Python (Black, Ruff), Go (gofmt), and Rust (rustfmt). Each one is a small bash script wrapped around the existing CLI, so they're transparent, easy to audit, and use the same config your team already committed.

The pattern: Every formatter hook is a PostToolUse hook matching Write|Edit. When Claude writes a file with the matching extension, the hook runs the formatter. If the formatter succeeded, the operation continues; if it found errors it can't fix, the output goes back to Claude so it can address them on the same turn. That's the whole loop.

Anatomy of auto-prettier

bash .claude/hooks/auto-prettier.sh
#!/bin/bash # Runs prettier --write on files Claude wrote or edited. # Silent on success; passes formatter errors back to Claude on failure. input=$(cat) file_path=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.path // ""') # Skip if no file, or file extension isn't a prettier target if [ -z "$file_path" ] || ! [[ "$file_path" =~ \.(js|jsx|ts|tsx|css|scss|html|json|md|yaml|yml)$ ]]; then exit 0 fi # Run prettier with team config; capture errors output=$(npx prettier --write "$file_path" 2>&1) result=$? if [ $result -ne 0 ]; then echo "Prettier failed on $file_path:" echo "$output" exit 1 # Warn Claude but don't block the write fi exit 0 # Silent success

Twenty-five lines of bash, and Prettier now runs on every JavaScript, TypeScript, CSS, HTML, JSON, Markdown, or YAML file Claude touches. Notice the exit 1 on failure: that's warn, not block. Formatter failure shouldn't stop the whole operation — better to keep going and let Claude notice the formatting complaint on the next turn.

The exit-code discipline that makes formatters comfortable to install

This is the design decision people get wrong on their first formatter hook and regret immediately:

  • Formatter successexit 0. Silent, no output. This should be 99% of runs.
  • Formatter can't fix the file (syntax error, config problem) → exit 1. Non-blocking warn. Claude sees the error and usually fixes it on the same turn.
  • Formatter isn't installed or misconfiguredexit 0 with a stderr log. Don't punish Claude for a missing dev dependency.

The instinct is to exit 2 (block) on any formatter error — don't. A blocking formatter turns every syntax mistake into a wall Claude can't get past on the current message. Warning lets Claude see the issue and fix it next turn.

Linters and type-checkers in the same category

Three of the seven hooks aren't strictly "formatters" — they're static checks that also live in PostToolUse:

  • auto-eslint — runs eslint --fix. Auto-fixes what it can, warns about what it can't.
  • auto-ruff — runs ruff check --fix and ruff format. Ruff is fast enough that you can run both on every write.
  • auto-typecheck — runs tsc --noEmit on the affected package. Not fast, but essential for catching type errors before they compound.

Same exit-code discipline applies. Warn, don't block. The exception is auto-typecheck on some teams — if you care deeply about type safety, blocking on type errors is defensible. Most teams find warning gives Claude enough signal without slowing interactive work.

Performance — the one gotcha

Every PostToolUse hook runs on every matching write. For a session with 30 file edits, that's 30 formatter runs. Rules to keep it comfortable:

  • Format single files, not the project. prettier --write $file_path, not prettier --write ..
  • Skip type-check on unrelated packages in a monorepo. auto-typecheck should read the file path and only check the containing package.
  • Prefer Ruff over Black + Flake8 if you're on Python. Ruff is ~100x faster and handles both jobs.
  • Skip generated files. Add early exits for paths matching dist/, node_modules/, .next/, __generated__/. Formatting generated files wastes cycles.
Decision Framework

Which formatter hooks should I install?

Start with your primary language; add hooks per additional language as needed. All coexist cleanly.

1.Install for your primary language first
TypeScript project? auto-prettier + auto-eslint. Python? auto-ruff. Go? auto-gofmt. This is the single highest install-to-value action in the whole hooks library.
2.Add auto-typecheck if type safety matters
Adds ~500ms per TS edit but catches type errors before they compound. Worth it on any project where types are load-bearing. Skip for prototype/exploration work.
3.Prefer Ruff on Python
auto-ruff is ~100x faster than Black + Flake8 and covers both jobs. Only use auto-black-python if your team has a specific Black-only convention already committed.
For monorepos
Configure hooks to only run the formatter for the affected package/workspace — don't run tsc across the whole tree on every edit. Read the file path in the hook and scope accordingly.
For polyglot projects
Install a hook per language. They coexist cleanly — each one matches on file extension, so auto-prettier, auto-ruff, and auto-gofmt all run without stepping on each other.
For heavy edit loops
Watch total hook latency. If your PostToolUse chain exceeds ~2 seconds per write, Claude interactions start to feel sluggish. Prefer Ruff over Black, skip tsc in fast-loop sessions, exclude generated files.

🐛 Hit an error while using these?

Our sister site AI Error Hub covers Claude Code errors, MCP connection failures, and stack traces — cross-referenced with everything on this site.

Visit AI Error Hub →
FAQ

Frequently asked questions

The questions developers ask most about formatter.

The one for your primary language:

  • TypeScriptauto-prettier + auto-eslint
  • Pythonauto-ruff
  • Goauto-gofmt
  • Rustauto-rustfmt

Formatter hooks have the highest install-to-value ratio in the whole hooks library: three lines of settings.json and Claude's output is formatted correctly forever.

Warn (exit 1), not block (exit 2).

Blocking on formatter failure means every syntax error becomes a wall Claude can't get past on the current turn. Warning lets Claude see the error message and fix it on the next turn — the actual desired behavior.

Reserve block-level severity for security hooks and destructive-operation gates.

A little, but usually not enough to notice — most formatters are sub-second:

  • Prettier on a single file — ~100ms
  • Ruff — ~30ms
  • gofmt — under 10ms

The exception is auto-typecheck, which can take a second or two depending on project size.

If you're editing many files in tight loops: skip type-check, use Ruff instead of Black+Flake8, exclude generated files, and scope typecheck to the affected package only.

auto-ruff, in almost every case:

  • ~100x faster than Black
  • Handles both formatting and linting
  • Formatter output matches Black's style by default

The only reason to prefer auto-black-python is if your team already committed a Black-specific config and doesn't want to switch. For new projects, use Ruff.

The hook should exit 0 (success) with a stderr log noting the missing dependency — not fail loudly and interrupt the workflow.

That way Claude Code sessions keep working for teammates who haven't installed the dev deps yet, while still leaving a paper trail so someone eventually notices and fixes it.

Don't punish the interaction for a missing dev dependency.

Yes — because they invoke the formatter's own CLI, which reads those ignore files natively. No extra config needed.

If your team already committed .prettierignore excluding generated dirs, the hook automatically respects that. Same for .eslintignore, Ruff's [tool.ruff] excludes, and rustfmt.toml's ignore patterns.

Yes, and it's common. auto-prettier + auto-eslint + auto-typecheck all run on TypeScript writes.

They chain in the order they're declared in settings.json. Just watch total latency — three formatters running on every write can add up.

In monorepos, keep typecheck scoped to the affected package to avoid the slowest hook running project-wide.

They're complementary layers:

  • Formatter hooks catch things as Claude writes them — immediate feedback, no drift accumulation.
  • Git pre-commit hooks catch things that reach the commit stage regardless of how they got there (Claude, another dev, an editor plugin).

Best practice: both. Formatter hooks for interactive Claude Code work, pre-commit for the git boundary.

No. Hooks fire on tool-use events regardless of who invoked the tool. When a subagent writes a file, the PostToolUse formatter hooks run just as they would in the main conversation.

This is important — team-shared formatters must apply uniformly. If security-reviewer or postgres-dba writes a migration file, it still gets formatted.

Yes, absolutely. Commit .claude/settings.json and .claude/hooks/ to git.

Every teammate gets the same formatters on next pull, with zero manual setup. This is exactly the pattern that makes Claude Code output indistinguishable from human output — no drift, no "wait, my Prettier is on a different version", no cleanup PRs.

Commit and forget.

Share with