CloudCodeTree LogoCloudCodeTree
AI NewsTutorialsAbout
CloudCodeTree Logo
CloudCodeTree
  • AI News
  • Tutorials
  • About
← Back to AI News
Ship Team-Wide Policy in Shell: Claude Code Hooks for Guards, Cleanup, and Remote Enforcement

Ship Team-Wide Policy in Shell: Claude Code Hooks for Guards, Cleanup, and Remote Enforcement

Chris Harper

3 min read

Jul 20, 2026 · 04:07 UTC

AI
Workflow
Claude Code
Best Practices

Claude Code hooks are shell scripts the harness runs at every tool call — Claude cannot skip them, making them the right layer for linters, secret guards, and auto-commit.

Prompts and instructions are suggestions. Hooks are contracts. When Claude edits a file, your PostToolUse hook runs ESLint before Claude even forms its next thought. When Claude tries to push to main, your PreToolUse hook can block it and return a message explaining why. The model sees the hook's feedback, not the raw shell exit code.

Four handler types

// .claude/settings.json  (commit this — all teammates get the same rules)
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{ "type": "command", "command": "scripts/secret-guard.sh" }]
    }],
    "PostToolUse": [{
      "matcher": "Edit",
      "hooks": [{ "type": "command", "command": "scripts/format.sh" }]
    }],
    "Stop": [{
      "hooks": [{ "type": "command", "command": "scripts/audit.sh" }]
    }]
  }
}
  • PreToolUse — fires before the tool runs; exit 2 to block and return a message to Claude; exit 0 to allow
  • PostToolUse — fires after a write; cannot undo the action, but perfect for auto-format, lint-on-save, and logging
  • Stop — runs when Claude finishes a turn; ideal for audit trails, Slack pings, or auto-commit
  • HTTP hooks (added Feb 2026) — POST the event JSON to your own endpoint instead of a local script; enables remote policy validation that works uniformly across every machine in your org

Copy-paste examples

Secret scanner (PreToolUse on Bash, blocks git push containing API keys):

#!/bin/bash
# scripts/secret-guard.sh — reads tool_input JSON from stdin
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')

# block pushes that would expose credentials
if echo "$COMMAND" | grep -q 'git push' 2>/dev/null; then
  if git diff --cached | grep -E '(AKIA[0-9A-Z]{16}|sk-ant-|ghp_[a-zA-Z0-9]{36})' >/dev/null 2>&1; then
    echo '{"decision":"block","reason":"API key detected in staged diff — remove it before pushing."}'
    exit 2
  fi
fi

Auto-format after every file edit (PostToolUse on Edit):

#!/bin/bash
# scripts/format.sh — input JSON via stdin
FILE=$(jq -r '.tool_input.path // ""' 2>/dev/null)
[ -z "$FILE" ] && exit 0
case "$FILE" in
  *.ts|*.tsx) npx prettier --write "$FILE" 2>/dev/null ;;
  *.py)       ruff format "$FILE" 2>/dev/null ;;
  *.go)       gofmt -w "$FILE" 2>/dev/null ;;
esac
exit 0  # never block on format failure

Team vs personal hooks

Hooks in .claude/settings.json are project-scoped and checked into git — everyone on the team gets the same guards. Personal overrides (e.g. a dev's preferred linter flags) go in .claude/settings.local.json (gitignored).

Matchers

The matcher field is a substring match against the tool name. An empty string ("") matches every tool — useful for logging all activity or HTTP hooks that enforce broad policies:

{ "matcher": "",     // all tools
  "hooks": [{ "type": "http", "url": "https://policy.example.com/check" }] }
{ "matcher": "Bash", // only shell commands }
{ "matcher": "Edit", // only file writes }
{ "matcher": "Write", "hooks": [...] }

Sources: Automate actions with hooks — Claude Code Docs, 15 Claude Code hook examples, Claude Code Hooks: Complete Guide