Claude Code Hooks, Subagents, and Piping: Advanced Automation for Teams

Most teams use Claude Code reactively — they type a prompt, Claude responds, they type another. That is fine, but it leaves significant value on the table. Hooks, subagents, and piping let you build Claude into your workflow so that it works with your tools rather than alongside them.


Hooks: Making Claude Anticipatory

A hook is a shell command, script, or HTTP call that fires automatically when Claude Code reaches a specific lifecycle point. Unlike CLAUDE.md instructions (which are advisory), hooks are deterministic — they always run.

flowchart TD
    SS[SessionStart] --> Work[Developer works]
    Work --> PTU[PreToolUse\nruns before each tool]
    PTU --> Tool[Tool executes\nBash Edit Write etc]
    Tool --> PTUS[PostToolUse\nruns after each tool]
    PTUS --> Work
    Work --> SE[SessionEnd]

Configuration

Hooks live in .claude/settings.json (project-wide) or ~/.claude/settings.json (personal):

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/on-edit.sh"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/pre-bash.sh"
          }
        ]
      }
    ]
  }
}

Hooks receive a JSON payload on stdin with the tool name, input, current working directory, and session ID — enabling conditional behaviour.

Hook: Auto-run tests after every edit

The most valuable hook for development teams. Every time Claude edits a file, tests run automatically. Claude sees the results immediately and can fix breaking changes without you prompting it.

#!/bin/bash
# .claude/hooks/on-edit.sh
input=$(cat)
CWD=$(echo "$input" | jq -r '.cwd // "."')

cd "$CWD"

# Run fast unit tests only, cap output
if [ -f "package.json" ]; then
  npm test --silent 2>&1 | tail -20
elif [ -f "go.mod" ]; then
  go test ./... 2>&1 | tail -20
elif [ -f "requirements.txt" ]; then
  python -m pytest --tb=short -q 2>&1 | tail -20
fi

exit 0

Hook: Block destructive Bash commands

A safety net that blocks commands matching a danger pattern and shows Claude why:

#!/bin/bash
# .claude/hooks/pre-bash.sh
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // ""')

DANGEROUS="rm -rf|kubectl delete|terraform destroy|DROP TABLE|truncate.*--"

if echo "$cmd" | grep -qiE "$DANGEROUS"; then
  jq -n --arg cmd "$cmd" '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "Blocked: destructive command pattern detected. Run manually after review."
    }
  }'
  exit 0
fi

exit 0

Hook: Auto-format on save

Format files in the appropriate language every time Claude edits them:

#!/bin/bash
# .claude/hooks/format.sh
input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.path // ""')

[ -z "$file" ] && exit 0

case "$file" in
  *.ts|*.tsx|*.js|*.jsx) npx prettier --write "$file" 2>/dev/null ;;
  *.py) black "$file" 2>/dev/null ;;
  *.go) gofmt -w "$file" 2>/dev/null ;;
  *.tf) terraform fmt "$file" 2>/dev/null ;;
esac

exit 0

Subagents: Parallel Context Windows

A subagent is a separate Claude instance that runs alongside your main session. It has its own context window, so it can investigate a large codebase without polluting your main conversation.

sequenceDiagram
    participant Dev as Developer
    participant Main as Main Session
    participant SA as Subagent

    Dev->>Main: Implement the new rate limiter
    Main->>SA: Spawn: read src/middleware/ and summarise the existing middleware chain
    SA->>SA: Reads files independently
    SA-->>Main: Summary: 3 middleware layers, auth runs before rate limit
    Main->>Main: Uses summary to implement correctly
    Main-->>Dev: Rate limiter implemented, sits after auth middleware

When to use subagents:

  • Investigating a large codebase to answer a specific question before main work begins
  • Running a security or code review scan in parallel with active development
  • Fetching external documentation or API specs without polluting your main session

How to invoke:

Use a subagent to investigate the payment service.
Read only: src/services/payment/, src/models/order.ts
Task: summarise the data flow from order creation to payment confirmation
Report back a concise summary — do not make any changes

Key rule: Keep subagent tasks independent. If a subagent needs to share state with the main session (edit the same file, for example), race conditions can corrupt work. Use subagents for read-only investigation, not write operations.


Piping: Claude in Your Shell Workflows

The -p flag makes Claude Code headless — single prompt in, stdout out. This enables Claude to participate in shell pipelines like any other Unix tool.

Basic piping

# Analyse log output
docker logs myapp --tail 200 | claude -p "what errors are here and what is causing them?"

# Summarise git history
git log --oneline -20 | claude -p "summarise what changed this week in plain English"

# Review a diff before committing
git diff --staged | claude -p "review this diff for bugs or issues before I commit"

Chaining Claude calls

# Generate migration, then review it
claude -p "generate a SQL migration to add a soft-delete column to the users table" \
  > migration.sql

claude -p "review this migration for safety — will it lock the table? Is rollback safe?" \
  < migration.sql

Integrating with CI

# Post a natural-language summary of a deploy to Slack
deploy_output=$(./deploy.sh 2>&1)
summary=$(echo "$deploy_output" | claude -p "summarise this deploy output in 2 sentences for a Slack message")
curl -X POST "$SLACK_WEBHOOK" -d "{\"text\": \"$summary\"}"

Writing Slack updates from git diffs

One of the highest-value daily uses of piping:

#!/bin/bash
# scripts/slack-standup.sh
diff=$(git log --since="yesterday" --oneline --all)
update=$(echo "$diff" | claude -p "write a casual 3-bullet Slack standup update from these commits. First person, past tense, no jargon.")
echo "$update"

Combining All Three

The most powerful pattern combines hooks, subagents, and piping:

flowchart TD
    Edit[Claude edits a file] --> Hook[PostToolUse hook fires]
    Hook --> Tests[Run test suite]
    Tests --> Fail{Tests pass?}
    Fail -->|Yes| Continue[Claude continues working]
    Fail -->|No| SA[Spawn subagent to diagnose failure]
    SA --> SA2[Subagent reads test output + edited file]
    SA2 --> Fix[Subagent suggests fix]
    Fix --> Main[Main session applies fix]
    Main --> Edit

The key insight: hooks make Claude notice things without being asked. Subagents investigate without bloating main context. Piping exports Claude’s output into other tools. Together they turn Claude from a chatbot into a workflow participant.


Getting Started Checklist

  1. Add the auto-format hook first — low risk, immediate value
  2. Add the destructive command block — safety net with no false positive risk
  3. Try the test-on-edit hook in a project with fast tests
  4. Use piping for one daily task (log analysis or git diff summary)
  5. Try one subagent investigation before implementing a feature

Each of these can be added independently. Start with one and see the difference before adding more.

Abhay

Abhay Pratap Singh

DevOps Engineer passionate about automation, cloud infrastructure, and self-hosted tools. I write about Kubernetes, Terraform, DNS, and everything in between.