Draft / RFC — 2026-04-14 · TrueFoundry Engineering. Internal design reference; endpoints and behavior may change before general availability.
1. Executive Summary
AI coding assistants (Cursor, Claude Code, and others emerging) now support hooks — lifecycle callbacks that let external systems observe, control, and extend the agent loop. TrueFoundry’s AI Gateway already has a working Cursor hooks endpoint (/cursor/hooks/validate). This document maps the exact API surface of each client, identifies gaps, and proposes a unified architecture to support all clients through the gateway’s guardrail engine.
2. Current State (What We Have)
Existing Endpoint
POST /cursor/hooks/validate
POST /api/llm/cursor/hooks/validate
Implementation Files:
- Router:
src/routers/cursorHooks.ts
- Handler:
src/handlers/cursorHooksHandler.ts
Current Request Body:
{
"hook_event_name": "beforeSubmitPrompt | beforeShellExecution | beforeReadFile | afterFileEdit | afterAgentResponse | beforeMCPExecution | afterMCPExecution",
"text": "content to validate",
"conversation_id": "string",
"generation_id": "string",
"workspace_roots": ["string"],
"metadata": { "server": "mcp-server-name", "tool_name": "tool-name" }
}
Current Response Body:
{
"continue": true | false,
"permission": "allow" | "deny",
"userMessage": "string",
"agentMessage": "string",
"guardrail_checks": [
{ "id": "string", "verdict": true|false, "error": "string", "data": {}, "execution_time": 123 }
]
}
What it does: Maps hook events to guardrail types (input/output/mcp-pre/mcp-post), resolves applicable guardrails for the user/tenant, executes them, and returns allow/deny.
3. Cursor Hooks API — Complete Reference
3.1 Protocol
| Aspect | Detail |
|---|
| Transport | Hooks are local child processes communicating via stdin/stdout JSON. No HTTP involved natively. |
| Registration | File-based: .cursor/hooks.json (project), ~/.cursor/hooks.json (user), MDM-managed (enterprise) |
| Exit codes | 0 = success (parse stdout), 2 = block/deny, other = fail-open |
| Fail behavior | Default: fail-open. Set failClosed: true to block on hook errors |
{
"version": 1,
"hooks": {
"<eventName>": [
{
"command": "./path/to/script.sh",
"type": "command" | "prompt",
"timeout": 30,
"loop_limit": 5,
"failClosed": false,
"matcher": "regex-pattern"
}
]
}
}
3.3 All Hook Events
| Event | Category | Matcher Target |
|---|
sessionStart | Session | — |
sessionEnd | Session | — |
beforeSubmitPrompt | Pre-input | — |
preToolUse | Pre-tool | Tool name (Shell|Read|Write|Task|MCP) |
postToolUse | Post-tool | Tool name |
postToolUseFailure | Post-tool | Tool name |
beforeShellExecution | Pre-tool (shell) | — |
afterShellExecution | Post-tool (shell) | — |
beforeMCPExecution | Pre-MCP | — |
afterMCPExecution | Post-MCP | — |
beforeReadFile | Pre-file | — |
afterFileEdit | Post-file | — |
afterAgentResponse | Post-response | — |
afterAgentThought | Post-thought | — |
subagentStart | Subagent | — |
subagentStop | Subagent | — |
stop | Lifecycle | — |
preCompact | Compaction | — |
beforeTabFileRead | Tab (inline) | — |
afterTabFileEdit | Tab (inline) | — |
Common base (all events):
{
"conversation_id": "string",
"generation_id": "string",
"model": "string",
"hook_event_name": "string",
"cursor_version": "string",
"workspace_roots": ["string"],
"user_email": "string | null",
"transcript_path": "string | null"
}
preToolUse input:
{
"tool_name": "Shell|Read|Write|Task|MCP",
"tool_input": { "command": "..." },
"tool_use_id": "string",
"cwd": "string",
"agent_message": "string"
}
preToolUse output:
{
"permission": "allow|deny",
"user_message": "string",
"agent_message": "string",
"updated_input": {}
}
beforeShellExecution input:
{ "command": "string", "cwd": "string", "sandbox": false }
beforeMCPExecution input:
{ "tool_name": "string", "tool_input": "string", "url": "string" | "command": "string" }
beforeSubmitPrompt input:
{ "prompt": "string", "attachments": [{ "type": "file|rule", "file_path": "string" }] }
Permission output (shared across pre-hooks):
{ "permission": "allow|deny|ask", "user_message": "string", "agent_message": "string" }
3.5 How Cursor Calls External Hooks (Gateway Integration Pattern)
Cursor hooks are local processes. To route through the gateway, teams deploy a thin CLI script as the hook command that:
- Reads JSON from stdin
- POSTs it to
https://gateway.example.com/cursor/hooks/validate
- Reads the gateway response
- Exits with code
0 (allow) or 2 (deny)
This is the bridge pattern our current implementation relies on.
4. Claude Code Hooks API — Complete Reference
4.1 Protocol
| Aspect | Detail |
|---|
| Transport | Four handler types: command (stdin/stdout), http (POST), prompt (LLM eval), agent (sub-agent) |
| Registration | ~/.claude/settings.json (user), .claude/settings.json (project), .claude/settings.local.json (local), managed policy (enterprise), plugin hooks/hooks.json |
| Exit codes | 0 = success, 2 = block, other = non-blocking error |
| Fail behavior | Default: fail-open. |
| Key difference | Native HTTP hook support — can POST directly to gateway without a CLI bridge |
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Edit|Write",
"hooks": [
{
"type": "command" | "http" | "prompt" | "agent",
"command": "./script.sh",
"url": "https://gateway.example.com/hooks/validate",
"headers": { "Authorization": "Bearer $TFY_API_KEY" },
"allowedEnvVars": ["TFY_API_KEY"],
"timeout": 30,
"if": "Bash(rm *)",
"statusMessage": "Checking guardrails...",
"async": false,
"once": false
}
]
}
]
}
}
4.3 All Hook Events
| Event | Category | Matcher Target |
|---|
SessionStart | Session | Source: startup|resume|clear|compact |
SessionEnd | Session | Reason: clear|resume|logout|… |
UserPromptSubmit | Pre-input | — |
PreToolUse | Pre-tool | Tool name (Bash|Edit|Write|Read|Glob|Grep|WebFetch|Agent|mcp__*) |
PostToolUse | Post-tool | Tool name |
PostToolUseFailure | Post-tool | Tool name |
PermissionRequest | Permission | Tool name |
PermissionDenied | Permission | Tool name |
Stop | Lifecycle | — |
StopFailure | Lifecycle | Error type: rate_limit|authentication_failed |
SubagentStart | Subagent | Agent type: Explore|Bash|Plan |
SubagentStop | Subagent | Agent type |
PreCompact / PostCompact | Compaction | manual|auto |
Notification | Async | notification_type |
WorktreeCreate / WorktreeRemove | Async | — |
InstructionsLoaded | Async | File path |
ConfigChange | Async | Config source |
CwdChanged | Async | — |
FileChanged | Async | Filename pattern |
TaskCreated / TaskCompleted | Async | — |
TeammateIdle | Async | — |
Elicitation / ElicitationResult | MCP | Server name |
Common base (all events):
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "default|plan|acceptEdits|auto|dontAsk|bypassPermissions",
"hook_event_name": "string",
"agent_id": "string",
"agent_type": "string"
}
PreToolUse input:
{
"tool_name": "Bash|Edit|Write|Read|Glob|Grep|WebFetch|Agent|mcp__server__tool",
"tool_input": {
"command": "npm test",
"description": "Run tests",
"timeout": 120000
},
"tool_use_id": "toolu_01ABC..."
}
PreToolUse output:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow|deny|ask|defer",
"permissionDecisionReason": "string",
"updatedInput": {},
"additionalContext": "string"
}
}
UserPromptSubmit input:
{ "prompt": "user's text" }
UserPromptSubmit output:
{
"decision": "block|none",
"reason": "string",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "string"
}
}
Universal output fields (all events):
{
"continue": true,
"stopReason": "string",
"suppressOutput": false,
"systemMessage": "Warning shown to user",
"decision": "block|none",
"reason": "string",
"hookSpecificOutput": { "hookEventName": "...", "...": "..." }
}
Permission decision precedence: deny > defer > ask > allow
4.5 HTTP Hook Type (Direct Gateway Integration)
This is the critical difference from Cursor. Claude Code can POST directly to our gateway:
{
"type": "http",
"url": "https://llm-gateway.truefoundry.com/api/llm/hooks/validate",
"headers": {
"Authorization": "Bearer $TFY_API_KEY"
},
"allowedEnvVars": ["TFY_API_KEY"],
"timeout": 30
}
- Request body = full event JSON sent as POST body
- Response = JSON parsed for decision/output
- Non-2xx = non-blocking error (execution continues)
- Must return 2xx with JSON body containing deny decision to actually block
5. Side-by-Side Comparison
| Dimension | Cursor | Claude Code |
|---|
| Protocol | stdin/stdout (child process only) | stdin/stdout, HTTP, prompt, agent |
| Direct HTTP to gateway | No (needs CLI bridge script) | Yes (native) |
| Config location | .cursor/hooks.json | .claude/settings.json |
| Config schema version | { "version": 1, "hooks": {...} } | { "hooks": {...} } |
| Event naming | camelCase (beforeShellExecution) | PascalCase (PreToolUse) |
| Permission values | allow|deny|ask | allow|deny|ask|defer |
| Output structure | Flat { permission, user_message } | Nested { hookSpecificOutput: { permissionDecision } } |
| Input mutation | updated_input in preToolUse | updatedInput in hookSpecificOutput |
| Fail behavior | failClosed per-hook | Default fail-open (no per-hook override) |
| Auth in hooks | user_email env var | headers with env var interpolation |
| MCP tool naming | metadata.server + metadata.tool_name | mcp__<server>__<tool> in tool_name |
| Total event types | ~20 | ~30+ |
| Shared events | preToolUse, postToolUse, beforeShellExecution, afterFileEdit, beforeSubmitPrompt, beforeMCPExecution, afterMCPExecution, sessionStart/End, subagentStart/Stop, preCompact, stop | All of Cursor’s + PermissionRequest, PermissionDenied, StopFailure, Notification, WorktreeCreate/Remove, InstructionsLoaded, ConfigChange, FileChanged, TaskCreated/Completed, TeammateIdle, Elicitation, PostCompact |
6. Engineering Design Decisions
Decision 1: Unified vs. Client-Specific Endpoints
Option A: Single unified endpoint /api/llm/hooks/validate
- Normalize all client formats into a common internal representation
- Pro: One handler, one guardrail resolution path
- Con: Must handle divergent schemas, risk of lowest-common-denominator
Option B: Client-specific endpoints /cursor/hooks/validate, /claude-code/hooks/validate, etc.
- Each client gets a dedicated handler with native schema support
- Pro: Full fidelity, easier to debug, client-specific features
- Con: Code duplication, more maintenance
Option C (Recommended): Unified core + client adapters
- Thin adapter layer per client that normalizes to internal format
- Shared guardrail resolution and execution engine (already exists)
- Client-specific response serialization
┌─────────────┐ ┌──────────────┐ ┌─────────────────────┐
│ Cursor │───>│ Cursor │───>│ │
│ CLI bridge │ │ Adapter │ │ Guardrail Engine │
└─────────────┘ └──────────────┘ │ (HooksManager + │
│ Plugin System) │
┌─────────────┐ ┌──────────────┐ │ │
│ Claude Code │───>│ Claude Code │───>│ │
│ HTTP hook │ │ Adapter │ └─────────────────────┘
└─────────────┘ └──────────────┘
QUESTION: Do we want to invest in a full adapter pattern now or start with Claude Code endpoint only (since it has native HTTP) and keep Cursor as-is?
The current Cursor response format is flat:
{ "continue": true, "permission": "allow", "userMessage": "", "agentMessage": "" }
Claude Code expects nested output:
{
"continue": true,
"decision": "none",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"additionalContext": ""
}
}
QUESTION: Should the Claude Code adapter return the exact Claude Code schema, or can we get away with just the common fields (continue, decision, reason)? Need to test what Claude Code actually requires vs. what it tolerates.
Decision 3: Event Mapping to Guardrail Types
Current mapping (Cursor):
| Hook Event | Guardrail Type |
|---|
beforeSubmitPrompt, beforeShellExecution, beforeReadFile | llmInputGuardrails |
afterFileEdit, afterAgentResponse | llmOutputGuardrails |
beforeMCPExecution | mcpToolPreInvokeGuardrails |
afterMCPExecution | mcpToolPostInvokeGuardrails |
New events from Claude Code that need mapping:
| Claude Code Event | Proposed Guardrail Type | Notes |
|---|
UserPromptSubmit | llmInputGuardrails | Same as beforeSubmitPrompt |
PreToolUse (Bash) | llmInputGuardrails | Same as beforeShellExecution |
PreToolUse (Edit/Write) | llmInputGuardrails | Same as beforeReadFile |
PreToolUse (mcp__*) | mcpToolPreInvokeGuardrails | MCP tool call |
PostToolUse | llmOutputGuardrails | Same as afterFileEdit/afterAgentResponse |
PostToolUse (mcp__*) | mcpToolPostInvokeGuardrails | MCP tool result |
Stop | New: agentStopGuardrails? | No current equivalent |
PermissionRequest | New: permissionGuardrails? | Could auto-approve/deny |
SessionStart | Not a guardrail | Observability/analytics only |
Notification | Not a guardrail | Observability/analytics only |
QUESTION: Which Claude Code events do we actually want to run guardrails on? The full list has 30+ events — most are observability. Should we start with just the blocking events (UserPromptSubmit, PreToolUse, PostToolUse) and treat the rest as analytics passthrough?
The current handler extracts text from the request body and wraps it as:
const requestParams = { messages: [{ role: 'user', content: text }], model: cacheIdentifier };
Claude Code sends structured tool input, not just text:
{
"tool_name": "Bash",
"tool_input": { "command": "rm -rf /", "description": "Clean up" }
}
QUESTION: How should we extract the “text to validate” from Claude Code’s structured input?
- Option A:
JSON.stringify(tool_input) — simple but loses semantic meaning
- Option B: Extract the primary field per tool type (
command for Bash, content for Write, file_path for Read)
- Option C: Send the full structured input to guardrails and let each plugin decide what to inspect
Decision 5: Authentication for Claude Code HTTP Hooks
Claude Code supports header interpolation from env vars:
{ "headers": { "Authorization": "Bearer $TFY_API_KEY" }, "allowedEnvVars": ["TFY_API_KEY"] }
QUESTION: Do we use the existing JWT auth flow (user must have a TFY API key)? Or do we create a lighter-weight hook-specific API key? Considerations:
- JWT flow requires the user to have a TrueFoundry account
- Hook-specific key could be scoped to just guardrail validation
- Enterprise deployment: how does the admin distribute credentials?
Both Cursor and Claude Code support mutating the tool input via hook response:
- Cursor:
{ "updated_input": { "command": "safe-version" } }
- Claude Code:
{ "hookSpecificOutput": { "updatedInput": { "command": "safe-version" } } }
QUESTION: Should our guardrails be able to mutate inputs (e.g., sanitize a shell command, redact PII from a prompt)? This is powerful but risky. Current implementation only does allow/deny.
Decision 7: Streaming / Latency Requirements
Hooks are in the critical path of every tool call. If the gateway is slow:
- Cursor: User sees a hang (script blocked on HTTP POST)
- Claude Code: User sees custom
statusMessage spinner
QUESTION: What is the acceptable p99 latency for hook validation?
- Current guardrail execution times?
- Should we implement a fast-path for common cases (e.g., cache allow decisions for repeated tool types)?
- Should we support async guardrails that don’t block the agent?
Decision 8: Additional Context Injection
Claude Code hooks can inject additionalContext — text that gets added to the conversation for the LLM to see. This is a powerful feature for:
- Adding security policies inline
- Warning about sensitive operations
- Providing organization-specific instructions
QUESTION: Should our gateway be able to inject context (not just allow/deny)? What would the guardrail plugin interface look like for this?
Decision 9: defer Permission (Claude Code Only)
Claude Code supports a 4th permission level: defer (let Claude decide, but show the user). This is between allow and ask.
QUESTION: Should we support defer in our response? This would require guardrails to have a “soft warning” mode in addition to hard block.
Decision 10: Other Clients (Future-Proofing)
Emerging AI coding assistants likely to adopt hooks:
- Windsurf (Codeium) — likely similar to Cursor’s local-process model
- GitHub Copilot — may adopt HTTP hooks
- JetBrains AI — may adopt HTTP hooks
- Zed AI — early stage
- Aider, Continue.dev — OSS tools
QUESTION: How much should we invest in a generic adapter pattern now vs. adding clients one at a time?
7. Proposed Architecture
┌─────────────────────────────────────────────┐
│ TFY LLM Gateway │
│ │
Cursor │ ┌──────────────┐ │
(CLI bridge) ────>│ │ /cursor/ │ │
│ │ hooks/ │──┐ │
│ │ validate │ │ ┌──────────────────┐ │
│ └──────────────┘ │ │ │ │
│ ├─>│ Hooks Adapter │ │
Claude Code │ ┌──────────────┐ │ │ Layer │ │
(HTTP hook) ─────>│ │ /claude-code/│ │ │ │ │
│ │ hooks/ │──┘ │ - Normalize │ │
│ │ validate │ │ input │ │
│ └──────────────┘ │ - Map events │ │
│ │ - Extract text │ │
│ ┌──────────────┐ │ │ │
Future Client ───>│ │ /hooks/ │────>│ │ │
│ │ validate │ └────────┬─────────┘ │
│ └──────────────┘ │ │
│ v │
│ ┌──────────────────────┐ │
│ │ Guardrail Engine │ │
│ │ (HooksManager) │ │
│ │ │ │
│ │ - Resolve rules │ │
│ │ - Execute plugins │ │
│ │ - Collect verdicts │ │
│ └────────┬─────────────┘ │
│ │ │
│ ┌────────v─────────────┐ │
│ │ Response Adapter │ │
│ │ (per-client format) │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────┘
interface NormalizedHookEvent {
// Source client
client: 'cursor' | 'claude-code' | 'generic';
// Normalized event category
category: 'input' | 'output' | 'mcp-pre' | 'mcp-post' | 'lifecycle' | 'analytics';
// Original event name (preserved for logging/debugging)
originalEventName: string;
// Content to validate
content: string;
// Structured input (for guardrails that need it)
structuredInput?: Record<string, unknown>;
// Tool context
toolName?: string;
toolType?: 'shell' | 'file-read' | 'file-write' | 'file-edit' | 'mcp' | 'web' | 'agent' | 'other';
// MCP context
mcpServer?: string;
mcpTool?: string;
// Session context
sessionId?: string;
conversationId?: string;
// User context (from auth middleware)
user: Session;
metadata: Record<string, unknown>;
}
8. Implementation Plan
Phase 1: Claude Code HTTP Hook Endpoint (Week 1-2)
- Create
src/routers/claudeCodeHooks.ts and src/handlers/claudeCodeHooksHandler.ts
- Mount at
/claude-code/hooks/validate and /api/llm/claude-code/hooks/validate
- Handle
PreToolUse, PostToolUse, UserPromptSubmit events
- Parse Claude Code input format, map to guardrail types
- Return Claude Code response format (
hookSpecificOutput)
- Auth via Bearer token header (existing JWT flow)
Phase 2: Adapter Layer Refactor (Week 2-3)
- Extract common guardrail resolution logic from
cursorHooksHandler.ts
- Create
src/hooks/adapters/cursor.ts and src/hooks/adapters/claudeCode.ts
- Create
src/hooks/normalizedEvent.ts for internal format
- Refactor both handlers to use shared core
Phase 3: Advanced Features (Week 3-4)
- Input mutation support (return modified tool inputs)
- Additional context injection
defer permission support
- Analytics passthrough for non-blocking events (SessionStart, Stop, etc.)
Phase 4: Distribution & Onboarding (Week 4-5)
- CLI bridge script for Cursor (npm package or downloadable script)
- Claude Code
.claude/settings.json template generator
- TrueFoundry dashboard UI for hooks configuration
- Documentation and onboarding guide
9. Open Questions (Sorted by Priority)
P0 — Must answer before implementation
| # | Question | Context |
|---|
| 1 | Which Claude Code events should we run guardrails on? | 30+ events exist. Recommend starting with UserPromptSubmit, PreToolUse, PostToolUse only. The rest are analytics. |
| 2 | What is the exact response format Claude Code requires? | Need to test: does Claude Code work with just { "decision": "block", "reason": "..." } or does it need the full hookSpecificOutput nesting? |
| 3 | How do we extract “text to validate” from structured tool inputs? | Bash sends { command }, Write sends { content }, Edit sends { old_string, new_string }. Each tool has different primary content. |
| 4 | Do we use existing JWT auth or create hook-specific API keys? | Affects onboarding friction. JWT requires TFY account. |
P1 — Should answer before Phase 2
| # | Question | Context |
|---|
| 5 | Should we support input mutation? | Powerful but risky. E.g., rewriting a dangerous shell command. |
| 6 | Should we support additionalContext injection? | Can inject security policies into the LLM’s context window. |
| 7 | What is acceptable p99 latency for hook validation? | Hooks are in the critical path. Every ms matters. |
| 8 | Should we support the defer permission level? | Only Claude Code supports it. Adds complexity. |
| 9 | Unified endpoint or keep client-specific? | Affects API surface, routing, and documentation. |
P2 — Can defer to Phase 3+
| # | Question | Context |
|---|
| 10 | How do we handle analytics-only events? | SessionStart/End, Stop, Notification etc. Log them? Forward to OTEL? |
| 11 | Should we generate .cursor/hooks.json and .claude/settings.json from the dashboard? | Better DX but more surface area. |
| 12 | How do we distribute the Cursor CLI bridge script? | npm package? curl-installable? Bundled in TFY CLI? |
| 13 | Do we need to handle PermissionRequest to auto-configure Claude Code permissions? | This would let the gateway auto-allow/deny permission prompts. |
| 14 | How do we handle failClosed behavior differences? | Cursor has per-hook failClosed. Claude Code is always fail-open. Should gateway control this? |
| 15 | Future clients: invest in generic adapter now or add ad-hoc? | Windsurf, GitHub Copilot, JetBrains AI are likely next. |
10. Appendix: Client Configuration Examples
A. Cursor Setup (with CLI bridge)
.cursor/hooks.json:
{
"version": 1,
"hooks": {
"beforeSubmitPrompt": [
{
"command": "tfy-hook-bridge",
"timeout": 10,
"failClosed": true
}
],
"beforeShellExecution": [
{
"command": "tfy-hook-bridge",
"timeout": 10,
"failClosed": true
}
],
"afterFileEdit": [
{
"command": "tfy-hook-bridge",
"timeout": 10
}
],
"beforeMCPExecution": [
{
"command": "tfy-hook-bridge",
"timeout": 10,
"failClosed": true,
"matcher": ".*"
}
]
}
}
tfy-hook-bridge script (conceptual):
#!/bin/bash
INPUT=$(cat)
RESPONSE=$(echo "$INPUT" | curl -s -X POST \
-H "Authorization: Bearer $TFY_API_KEY" \
-H "Content-Type: application/json" \
-d @- \
"https://llm-gateway.truefoundry.com/cursor/hooks/validate")
PERMISSION=$(echo "$RESPONSE" | jq -r '.permission')
if [ "$PERMISSION" = "deny" ]; then
echo "$RESPONSE" >&2
exit 2
fi
echo "$RESPONSE"
exit 0
B. Claude Code Setup (native HTTP)
.claude/settings.json:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "http",
"url": "https://llm-gateway.truefoundry.com/api/llm/claude-code/hooks/validate",
"headers": {
"Authorization": "Bearer ${TFY_API_KEY}"
},
"allowedEnvVars": ["TFY_API_KEY"],
"timeout": 10,
"statusMessage": "Checking guardrails..."
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash|Edit|Write|mcp__.*",
"hooks": [
{
"type": "http",
"url": "https://llm-gateway.truefoundry.com/api/llm/claude-code/hooks/validate",
"headers": {
"Authorization": "Bearer ${TFY_API_KEY}"
},
"allowedEnvVars": ["TFY_API_KEY"],
"timeout": 10,
"statusMessage": "Validating tool use..."
}
]
}
],
"PostToolUse": [
{
"matcher": "Bash|Edit|Write|mcp__.*",
"hooks": [
{
"type": "http",
"url": "https://llm-gateway.truefoundry.com/api/llm/claude-code/hooks/validate",
"headers": {
"Authorization": "Bearer ${TFY_API_KEY}"
},
"allowedEnvVars": ["TFY_API_KEY"],
"timeout": 10
}
]
}
]
}
}
11. References