Feature: typed/composable hook definitions (beyond shell scripts)
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:
- 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
- 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.
- Hardcoded paths — the
settings.jsonhook definitions require absolute paths to scripts, making them non-portable:
``json``
{"type": "command", "command": "bash /mnt/c/Users/me/project/hooks/safety-gate.sh"}
- 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.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗