Claude Code Hook Recipe: Auto Format + Lint After Every Edit (PostToolUse)
A PostToolUse hook that makes Claude Code automatically run a formatter (Prettier/Black/gofmt) plus a linter every time it edits or writes a file—permanently removing 'inconsistent formatting' as a code review chore.
# 目標:Claude Code 每次編輯檔案後,自動格式化+lint
# 放到 .claude/settings.json(專案層級)或 ~/.claude/settings.json(全域)。
# 機制:PostToolUse hook 在 Edit / Write / MultiEdit 完成「之後」觸發,
# hook 從 stdin 收到 JSON,裡面 tool_input.file_path 是剛被改的檔案。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "bash {{PROJECT_DIR}}/.claude/hooks/format-after-edit.sh"
}
]
}
]
}
}
# ---- .claude/hooks/format-after-edit.sh ----
# chmod +x 後使用。依副檔名挑工具,找不到工具就安靜跳過(不要讓 hook 把流程弄爆)。
#!/usr/bin/env bash
set -euo pipefail
# 從 stdin 的 JSON 取出剛被編輯的檔案路徑
file=$(jq -r '.tool_input.file_path // empty')
[ -z "$file" ] && exit 0
[ ! -f "$file" ] && exit 0
case "$file" in
*.ts|*.tsx|*.js|*.jsx|*.mjs|*.json|*.css|*.md)
# JS/TS 系:先 format 再 lint --fix(工具不存在就略過)
command -v {{PRETTIER_CMD}} >/dev/null 2>&1 && {{PRETTIER_CMD}} --write "$file" >/dev/null 2>&1 || true
command -v {{ESLINT_CMD}} >/dev/null 2>&1 && {{ESLINT_CMD}} --fix "$file" >/dev/null 2>&1 || true
;;
*.py)
command -v {{PY_FORMATTER}} >/dev/null 2>&1 && {{PY_FORMATTER}} "$file" >/dev/null 2>&1 || true # 例:black / ruff format
command -v ruff >/dev/null 2>&1 && ruff check --fix "$file" >/dev/null 2>&1 || true
;;
*.go)
command -v gofmt >/dev/null 2>&1 && gofmt -w "$file" >/dev/null 2>&1 || true
;;
*.rs)
command -v rustfmt >/dev/null 2>&1 && rustfmt "$file" >/dev/null 2>&1 || true
;;
esac
exit 0
# 注意:PostToolUse 在工具已執行「之後」才跑,無法阻止編輯;
# 它的用途是「事後自動修好」,不是「事前攔截」。要攔截請用 PreToolUse。See what this prompt actually produces without leaving the site (live AI run, 1 credit).
Don't just copy-paste — download and drop it at ~/.claude/skills/hook-auto-format-lint-after-edit/SKILL.md and every future session can use it automatically.
mkdir -p ~/.claude/skills/hook-auto-format-lint-after-edit && mv ~/Downloads/SKILL.md ~/.claude/skills/hook-auto-format-lint-after-edit/SKILL.mdNew-Item -ItemType Directory -Force "$env:USERPROFILE\.claude\skills\hook-auto-format-lint-after-edit" | Out-Null; Move-Item "$env:USERPROFILE\Downloads\SKILL.md" "$env:USERPROFILE\.claude\skills\hook-auto-format-lint-after-edit\SKILL.md"## What This Is / What Problem It Solves One of the most annoying small things about letting an AI write code is inconsistent formatting: sometimes it uses 2 spaces, sometimes 4, import order, quote style, and trailing semicolons all vary slightly each time. The result is that half your code review time goes to whitespace diffs, and git blame gets polluted with meaningless formatting churn. This hook recipe wires 'format + lint --fix' into Claude Code's lifecycle: whenever the AI edits a file with Edit / Write / MultiEdit, a PostToolUse hook immediately runs the formatter and linter on that file, so by the time you see the diff it's already clean. ## Why This Source Is Worth Using Claude Code hooks are 'deterministic shell commands executed by the harness,' not a request for the AI to 'remember to format'—the AI can forget, a hook cannot. This recipe's PostToolUse structure (`matcher` targeting `Edit|Write`, `hooks[].type=command`, reading `tool_input.file_path` from a JSON payload on stdin) is taken directly from the working `auto-stage` PostToolUse hook in rohitg00/awesome-claude-code-toolkit. That hook originally 'auto-stages the file in git after an edit'; I rewrote the same trigger mechanism into 'auto format + lint after an edit'—a faithful extension of the same official mechanism, not something invented out of thin air. ## How to Use It (Steps) 1. Merge the JSON above into `.claude/settings.json` (or `~/.claude/settings.json` if you only want it applied to yourself). 2. Create `.claude/hooks/format-after-edit.sh`, paste in the script, and `chmod +x` it. 3. Make sure your machine has `jq` (the hook uses it to parse the stdin JSON) and whichever tools you need (prettier / eslint / black / ruff / gofmt, etc.). 4. Replace `{{PRETTIER_CMD}}`, `{{ESLINT_CMD}}`, `{{PY_FORMATTER}}` with your actual commands (e.g., `npx prettier`, `npx eslint`, `black`). 5. Confirm it's loaded in Claude Code with `/hooks`, then have the AI edit any file and check that it gets auto-formatted. ## Key Points - **Fail silently if a tool is missing** (`|| true`): don't let the hook break the entire editing flow just because some tool isn't installed on a given machine. - **Only run on the single file that changed**: format via `file_path`, not the whole repo—faster, and it won't touch unrelated files. - **PostToolUse cannot block an edit**: it runs 'after' the tool executes, so its role is 'automatic cleanup after the fact.' To intercept a dangerous action before it happens, use PreToolUse instead (see the other recipe on this site for a dangerous-command guardrail). ## When to Use It Most valuable for teams with shared formatter/linter configs where Claude Code frequently edits a lot of files at once; also great for personal projects where you want a clean git history where every diff is purely a real logic change. If your formatter is slow (running across an entire large monorepo), be sure to scope it to a single file, not the whole project, so every edit doesn't get stuck waiting. 📎 Source: rohitg00/awesome-claude-code-toolkit (author: Rohit Ghumare, Apache-2.0 license) — this piece's PostToolUse hook structure is adapted from its auto-stage hook, rewritten in Traditional Chinese and extended from 'auto git stage' to 'auto format + lint'; see the link above for the original.
[PROJECT_DIR]專案根目錄路徑,hook 腳本所在位置(也可用相對路徑 .claude/hooks/...)
[PRETTIER_CMD]Prettier 執行指令,例如 npx prettier 或 pnpm prettier
[ESLINT_CMD]ESLint 執行指令,例如 npx eslint 或 pnpm eslint
[PY_FORMATTER]Python formatter 指令,例如 black 或 ruff format
填下面的欄位,上方 prompt 會即時替換 [方括號] 內容。填好後按「複製組好的 prompt」直接丟進工具。
# 目標:Claude Code 每次編輯檔案後,自動格式化+lint
# 放到 .claude/settings.json(專案層級)或 ~/.claude/settings.json(全域)。
# 機制:PostToolUse hook 在 Edit / Write / MultiEdit 完成「之後」觸發,
# hook 從 stdin 收到 JSON,裡面 tool_input.file_path 是剛被改的檔案。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "bash {{PROJECT_DIR}}/.claude/hooks/format-after-edit.sh"
}
]
}
]
}
}
# ---- .claude/hooks/format-after-edit.sh ----
# chmod +x 後使用。依副檔名挑工具,找不到工具就安靜跳過(不要讓 hook 把流程弄爆)。
#!/usr/bin/env bash
set -euo pipefail
# 從 stdin 的 JSON 取出剛被編輯的檔案路徑
file=$(jq -r '.tool_input.file_path // empty')
[ -z "$file" ] && exit 0
[ ! -f "$file" ] && exit 0
case "$file" in
*.ts|*.tsx|*.js|*.jsx|*.mjs|*.json|*.css|*.md)
# JS/TS 系:先 format 再 lint --fix(工具不存在就略過)
command -v {{PRETTIER_CMD}} >/dev/null 2>&1 && {{PRETTIER_CMD}} --write "$file" >/dev/null 2>&1 || true
command -v {{ESLINT_CMD}} >/dev/null 2>&1 && {{ESLINT_CMD}} --fix "$file" >/dev/null 2>&1 || true
;;
*.py)
command -v {{PY_FORMATTER}} >/dev/null 2>&1 && {{PY_FORMATTER}} "$file" >/dev/null 2>&1 || true # 例:black / ruff format
command -v ruff >/dev/null 2>&1 && ruff check --fix "$file" >/dev/null 2>&1 || true
;;
*.go)
command -v gofmt >/dev/null 2>&1 && gofmt -w "$file" >/dev/null 2>&1 || true
;;
*.rs)
command -v rustfmt >/dev/null 2>&1 && rustfmt "$file" >/dev/null 2>&1 || true
;;
esac
exit 0
# 注意:PostToolUse 在工具已執行「之後」才跑,無法阻止編輯;
# 它的用途是「事後自動修好」,不是「事前攔截」。要攔截請用 PreToolUse。Suno Engineer's Mindset: 4 Steps to a Song That Doesn't Sound Like AI
A studio engineer's breakdown of Suno's fatal weaknesses (fried vocals, high-frequency artifacts), plus a 4-step DAW workflow and a Suno Studio cleanup prompt.
5 Claude Weekly Workflows That Stuck After 6 Months
Proposal generator / meeting processor / content repurposer / Friday review / shutdown reset — out of 40 I tried, only these 5 survived, each saving 30+ minutes per run.