Feature: typed/composable hook definitions (beyond shell scripts)

Status Closed — duplicate
Maintainer reply None cached
Activity 3 comments · opened Mar 9, 2026 · closed Mar 13, 2026

Problem

The current hook system (PreToolUse, PostToolUse) is powerful but the implementation is limited to shell commands that parse JSON from stdin. In practice, this means:

  1. Fragile pattern matching — blocking dangerous commands requires regex against serialized JSON:

``bash
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$CMD" | grep -qiE '(git\s+reset\s+--hard|git\s+push\s+--force)'; then
echo "BLOCKED" >&2; exit 2
fi
``

  1. No composability — to combine "block destructive ops" + "block deploys" + "block secrets", you either write one monolithic script or chain multiple hooks with no shared state.
  1. Hardcoded paths — the settings.json hook definitions require absolute paths to scripts, making them non-portable:

``json
{"type": "command", "command": "bash /mnt/c/Users/me/project/hooks/safety-gate.sh"}
``

  1. No access to context — hooks see the tool name and input but not the session context (current task, branch, working directory intent).

Proposal

Support TypeScript hook definitions alongside shell hooks:

// .claude/hooks/safety.ts
import { PreToolUseHook } from '@anthropic-ai/claude-code/hooks';

export const blockDestructive: PreToolUseHook = {
  matcher: 'Bash',
  validate({ toolInput }) {
    const cmd = toolInput.command;
    if (/git\s+(reset --hard|push --force|clean -f)/.test(cmd)) {
      return { block: true, reason: 'Destructive git operation blocked' };
    }
    return { block: false };
  }
};

export const blockDeploys: PreToolUseHook = {
  matcher: 'Bash',
  validate({ toolInput }) {
    const cmd = toolInput.command;
    if (/wrangler\s+deploy|kubectl\s+apply|terraform\s+apply/.test(cmd)) {
      return { block: true, reason: 'Production deploy blocked in autonomous mode' };
    }
    return { block: false };
  }
};

Benefits:

  • Type safety — tool input is typed, not raw JSON parsing
  • Composable — export multiple hooks from one file, combine policies
  • Portable — relative paths, no hardcoded absolute paths
  • Testable — unit test hook logic without spawning shell processes

Context

I maintain 5 safety hooks across 7 repositories for autonomous Claude Code task execution. The shell-based approach works but creates maintenance burden — every hook is ~30 lines of bash doing the same jq + grep pattern with slight variations. A typed API would reduce this to a few lines of TypeScript per policy.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗