Live
Black Hat USAAI BusinessBlack Hat AsiaAI BusinessWhy AI transparency is the key to richer instruction - University BusinessGoogle News: Generative AIMy parents spent all their money on my sister's rehab — now they want me to pay for it. How do I say no?Business InsiderOkta's CEO says all AI agents need a kill switchBusiness InsiderYour Divorce Attorney Wants You to Stop Using ChatGPT: Family Law, AI, and the Privilege You’re Giving Away - Ward and Smith, P.A.Google News: AIUkrainian troops showed 'greater tactical imagination' than Western trainers, British officer says, pointing to their ambush tacticsBusiness InsiderThese professors built AI tools that ask questions, instead of giving answers - The Washington PostGoogle News: AIOpenAI officially confirms mega-funding round and ChatGPT super appThe DecoderAnnouncing Doublehaven with Reflections on HumourLessWrong AIThe ghost of DOGE is still haunting Social SecurityBusiness InsiderIs the US headed for a recession? The Iran war could tip the balance.Business InsiderThe surprise winners of Trump's immigration warsAxios TechTrump's blurry vision of victory in IranAxios TechBlack Hat USAAI BusinessBlack Hat AsiaAI BusinessWhy AI transparency is the key to richer instruction - University BusinessGoogle News: Generative AIMy parents spent all their money on my sister's rehab — now they want me to pay for it. How do I say no?Business InsiderOkta's CEO says all AI agents need a kill switchBusiness InsiderYour Divorce Attorney Wants You to Stop Using ChatGPT: Family Law, AI, and the Privilege You’re Giving Away - Ward and Smith, P.A.Google News: AIUkrainian troops showed 'greater tactical imagination' than Western trainers, British officer says, pointing to their ambush tacticsBusiness InsiderThese professors built AI tools that ask questions, instead of giving answers - The Washington PostGoogle News: AIOpenAI officially confirms mega-funding round and ChatGPT super appThe DecoderAnnouncing Doublehaven with Reflections on HumourLessWrong AIThe ghost of DOGE is still haunting Social SecurityBusiness InsiderIs the US headed for a recession? The Iran war could tip the balance.Business InsiderThe surprise winners of Trump's immigration warsAxios TechTrump's blurry vision of victory in IranAxios Tech

I automated my entire dev workflow with Claude Code hooks

DEV Communityby Nox CraftApril 1, 202610 min read0 views
Source Quiz

<p>Most people use Claude Code as a smarter terminal assistant.<br> Type a request, read the response, approve the changes.<br> That's fine, but it leaves a lot of capability on the table.</p> <p>Claude Code has a hook system that wires the AI directly into your existing workflow:<br> your formatter, your test runner, your notification system.<br> We've been running hooks in production-style setups for a few months now<br> and the interaction model genuinely changes when the tool stops being conversational<br> and starts being ambient.</p> <p>Here's what we actually run and why.</p> <h2> What hooks are </h2> <p>Hooks are defined in <code>~/.claude/settings.json</code> under the <code>hooks</code> key.<br> Each hook fires at a lifecycle event and runs a shell command.</p> <p>The four events

Most people use Claude Code as a smarter terminal assistant. Type a request, read the response, approve the changes. That's fine, but it leaves a lot of capability on the table.

Claude Code has a hook system that wires the AI directly into your existing workflow: your formatter, your test runner, your notification system. We've been running hooks in production-style setups for a few months now and the interaction model genuinely changes when the tool stops being conversational and starts being ambient.

Here's what we actually run and why.

What hooks are

Hooks are defined in ~/.claude/settings.json under the hooks key. Each hook fires at a lifecycle event and runs a shell command.

The four events that matter:

  • PreToolUse -- fires before Claude runs a tool (file write, bash command, etc.)

  • PostToolUse -- fires after a tool completes

  • Notification -- fires when Claude sends status updates

  • Stop -- fires when Claude finishes a response

Hooks can be filtered by tool name, so you can target Bash separately from Write and Edit.

The basic structure:

{  "hooks": {  "PostToolUse": [  {  "matcher": "Write",  "hooks": [  {  "type": "command",  "command": "your-command-here"  }  ]  }  ]  } }

Enter fullscreen mode

Exit fullscreen mode

Hook 1: Auto-format on file write

Every time Claude writes or edits a file, format it immediately. This keeps diffs clean and removes a whole category of review noise.

{  "hooks": {  "PostToolUse": [  {  "matcher": "Write|Edit",  "hooks": [  {  "type": "command",  "command": "bash -c 'FILE=$(echo $CLAUDE_TOOL_OUTPUT | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get(\\\"filePath\\\",\\\"\\\"))\" 2>/dev/null); if [ -n \"$FILE\" ]; then case \"$FILE\" in *.rs) cargo fmt -- \"$FILE\" 2>/dev/null;; *.py) ruff format \"$FILE\" 2>/dev/null;; *.ts|*.tsx|*.js) npx prettier --write \"$FILE\" 2>/dev/null;; esac; fi'"  }  ]  }  ]  } }

Enter fullscreen mode

Exit fullscreen mode

The hook reads the file path from the tool output, detects the extension, and runs the right formatter. Silent on error (2>/dev/null) so it doesn't interrupt the session if a formatter isn't installed.

For Rust specifically, we also add a cargo check after writes. This catches type errors while Claude is still in context and can fix them in the same pass:

{  "matcher": "Write|Edit",  "hooks": [  {  "type": "command",  "command": "bash -c 'FILE=$(echo $CLAUDE_TOOL_OUTPUT | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get(\\\"filePath\\\",\\\"\\\"))\" 2>/dev/null); if echo \"$FILE\" | grep -q \"\\.rs$\"; then cargo check 2>&1 | tail -5; fi'"  }  ] }

Enter fullscreen mode

Exit fullscreen mode

Hook 2: Security scan before bash commands

Before Claude runs any shell command, scan for patterns worth flagging: piping to sh/bash from curl, rm -rf without bounds, writes to /etc/.

{  "hooks": {  "PreToolUse": [  {  "matcher": "Bash",  "hooks": [  {  "type": "command",  "command": "bash -c 'CMD=$(echo $CLAUDE_TOOL_INPUT | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get(\\\"command\\\",\\\"\\\"))\" 2>/dev/null); RISKY=0; echo \"$CMD\" | grep -qE \"curl.*\\|.*(bash|sh)\" && RISKY=1; echo \"$CMD\" | grep -qE \"rm -rf /[^t]\" && RISKY=1; echo \"$CMD\" | grep -q \"/etc/\" && RISKY=1; if [ $RISKY -eq 1 ]; then echo \"[hook] high-risk command flagged -- review before proceeding\"; fi'"  }  ]  }  ]  } }

Enter fullscreen mode

Exit fullscreen mode

This doesn't block the command. It prints a visible warning in the output so you catch it when skimming. We've caught a few genuine mistakes this way -- not AI hallucinations, just cases where a reasonable command had an unexpected side effect in context.

Hook 3: Desktop notification when Claude finishes

Switch to another window during a long task and you lose track of when it's done. The Stop hook fires on response completion.

{  "hooks": {  "Stop": [  {  "hooks": [  {  "type": "command",  "command": "notify-send 'Claude Code' 'Done.' --icon=terminal --urgency=low 2>/dev/null || true"  }  ]  }  ]  } }

Enter fullscreen mode

Exit fullscreen mode

On Ubuntu this uses notify-send. On macOS, swap it for osascript -e 'display notification "Done." with title "Claude Code"'.

For remote machines, we send to a Discord webhook instead:

{  "type": "command",  "command": "bash -c 'source ~/.secrets; curl -s -X POST \"$DISCORD_WEBHOOK_DEV\" -H \"Content-Type: application/json\" -d \"{\\\"content\\\": \\\"Claude finished a task\\\"}\" > /dev/null'" }

Enter fullscreen mode

Exit fullscreen mode

Hook 4: Custom status line with git context

The Notification hook fires when Claude sends progress updates (tool names, status messages). We use it to set the terminal title to show current branch and last action:

{  "hooks": {  "Notification": [  {  "hooks": [  {  "type": "command",  "command": "bash -c 'BRANCH=$(git branch --show-current 2>/dev/null || echo \"no-git\"); MSG=$(echo $CLAUDE_NOTIFICATION | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get(\\\"message\\\",\\\"\\\")[:40])\" 2>/dev/null); printf \"\\033]0;claude [%s] %s\\007\" \"$BRANCH\" \"$MSG\"'"  }  ]  }  ]  } }

Enter fullscreen mode

Exit fullscreen mode

The terminal title becomes something like claude [main] Writing src/main.rs. When you have multiple Claude sessions across different projects, this is the only sane way to tell them apart in your taskbar.

Hook 5: Test runner on source file changes

For TDD-style work, tests should run automatically after Claude modifies source files. The tight feedback loop matters: Claude writes code, tests run, Claude sees the result in the same context window and can iterate without prompting.

{  "hooks": {  "PostToolUse": [  {  "matcher": "Write|Edit",  "hooks": [  {  "type": "command",  "command": "bash -c 'FILE=$(echo $CLAUDE_TOOL_OUTPUT | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get(\\\"filePath\\\",\\\"\\\"))\" 2>/dev/null); if echo \"$FILE\" | grep -qE \"\\.(rs|py|ts)$\" && ! echo \"$FILE\" | grep -qE \"(test|spec)\"; then echo \"[hook] running tests...\"; if [ -f Cargo.toml ]; then cargo test 2>&1 | tail -10; elif [ -f pyproject.toml ]; then python -m pytest -x -q 2>&1 | tail -10; fi; fi'"  }  ]  }  ]  } }

Enter fullscreen mode

Exit fullscreen mode

Skips test files themselves to avoid infinite loops. Detects project type by manifest file. Tails 10 lines so the output stays readable.

Putting it together

The full hooks section in ~/.claude/settings.json:

{  "hooks": {  "PreToolUse": [  {  "matcher": "Bash",  "hooks": [{ "type": "command", "command": "..." }]  }  ],  "PostToolUse": [  {  "matcher": "Write|Edit",  "hooks": [  { "type": "command", "command": "..." },  { "type": "command", "command": "..." }  ]  }  ],  "Notification": [  {  "hooks": [{ "type": "command", "command": "..." }]  }  ],  "Stop": [  {  "hooks": [{ "type": "command", "command": "..." }]  }  ]  } }

Enter fullscreen mode

Exit fullscreen mode

Multiple hooks can fire for the same event and run in sequence.

Caveats worth knowing

PreToolUse hooks run synchronously -- a slow pre-hook adds latency before every tool call. Keep them under 100ms or use them sparingly.

PostToolUse hooks run after the tool completes, so slow post-hooks don't block Claude. They just add noise to the output, which is usually fine.

If a hook exits non-zero, Claude sees the output but continues. By default hooks are advisory, not blocking. There's a blocking mode available for PreToolUse, but we haven't needed it -- the warning output is enough.

The hook system is underused because it's not obvious that it exists. Once you wire Claude into your actual toolchain, the conversational back-and-forth collapses into something closer to a fast pair programmer who runs your checks automatically.

The full config is in a public gist at github.com/noxcraftdev if you want a starting point.

Happy coding!*

Was this article helpful?

Sign in to highlight and annotate this article

AI
Ask AI about this article
Powered by AI News Hub · full article context loaded
Ready

Conversation starters

Ask anything about this article…

Daily AI Digest

Get the top 5 AI stories delivered to your inbox every morning.

More about

claudemodelavailable

Knowledge Map

Knowledge Map
TopicsEntitiesSource
I automated…claudemodelavailableupdateproductapplicationDEV Communi…

Connected Articles — Knowledge Graph

This article is connected to other articles through shared AI topics and tags.

Knowledge Graph100 articles · 276 connections
Scroll to zoom · drag to pan · click to open

Discussion

Sign in to join the discussion

No comments yet — be the first to share your thoughts!

More in Products