DEV Community

Tobias Bieniek
Tobias Bieniek

Posted on

Using Claude Code Hooks for Notifications and Auto-Formatting

Claude Code introduced hooks on June 30, 2025, allowing users to execute shell commands at various points in the tool's lifecycle. Working primarily in IntelliJ's terminal, I encountered two specific issues that hooks helped address. Here are the scripts I implemented to handle notifications and automatic code formatting.

Problem: Missing Notifications in IntelliJ

Working primarily in IntelliJ's terminal meant Claude Code's built-in notification system wasn't working reliably with my setup. I would often miss when Claude was waiting for input or when tasks completed.

Solution 1: Desktop Notifications with notify.sh

I created a bash script that hooks into Claude Code's notification system and sends desktop notifications:

#!/bin/bash

# Read JSON input from stdin
json_input=$(cat)

# Extract information
title=$(echo "$json_input" | jq -r '.title // "Claude Code"')
message=$(echo "$json_input" | jq -r '.message // "Task completed"')

# Send notification
pnpx node-notifier-cli@2 \
    --title "$title" \
    --message "$message" \
    --sound "Tink"
Enter fullscreen mode Exit fullscreen mode

The script uses node-notifier-cli to send native desktop notifications with a sound. It parses the JSON input from Claude Code and extracts the title and message, providing defaults if they're missing.

Here's how it looks in action:

Claude Code notification showing task completion

To set this up, I added it to my Claude Code hooks configuration:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/notify.sh"
          }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

The empty matcher means it triggers for all notifications, which is exactly what I wanted.

Problem: Inconsistent Code Formatting

Claude Code doesn't always format Rust code the same way cargo fmt does. This was causing my CI pipeline to fail with formatting errors.

Solution 2: Automatic Formatting with format.sh

I created a second hook that automatically formats files after modification:

#!/bin/bash

# Read JSON input from stdin
json_input=$(cat)

# Extract information
file_path=$(echo "$json_input" | jq -r '.tool_response.filePath // ""')

# Run formatting tools based on file type
if [[ "$file_path" =~ \.rs$ ]]; then
    echo "Rust file modified, running cargo fmt --all..."
    cargo fmt --all
fi
Enter fullscreen mode Exit fullscreen mode

This script checks if a Rust file was modified and automatically runs cargo fmt --all to ensure consistent formatting across the project. The approach can be extended to other languages by adding more conditionals for different file extensions.

Here's the hook configuration for the PostToolUse event:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/format.sh"
          }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

The matcher targets file modification tools (Write, Edit, MultiEdit), so formatting only happens when files are actually changed.

Why Hooks Work Better Than Prompting

Hooks provide deterministic behavior rather than relying on the LLM to remember to format code or handle notifications. Instead of hoping Claude remembers to run cargo fmt, there's now a system-level guarantee that it happens every time.

Top comments (0)