Skip to main content
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

Implementation Files:
  • Router: src/routers/cursorHooks.ts
  • Handler: src/handlers/cursorHooksHandler.ts
Current Request Body:
Current Response Body:
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

3.2 Manifest Format (hooks.json)

3.3 All Hook Events

3.4 Key Input/Output Schemas

Common base (all events):
preToolUse input:
preToolUse output:
beforeShellExecution input:
beforeMCPExecution input:
beforeSubmitPrompt input:
Permission output (shared across pre-hooks):

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:
  1. Reads JSON from stdin
  2. POSTs it to https://gateway.example.com/cursor/hooks/validate
  3. Reads the gateway response
  4. 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

4.2 Manifest Format (settings.json)

4.3 All Hook Events

4.4 Key Input/Output Schemas

Common base (all events):
PreToolUse input:
PreToolUse output:
UserPromptSubmit input:
UserPromptSubmit output:
Universal output fields (all events):
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:
  • 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


6. Engineering Design Decisions

Decision summary: We will expose a single /guardrail API on the AI Gateway that any AI coding assistant hook can invoke. Cursor reaches it via a thin CLI bridge (because Cursor hooks are local processes), and Claude Code reaches it directly using its native HTTP hook type. The endpoint normalizes client-specific event schemas into a shared internal format, runs the existing guardrail engine, and serializes the response back into the format the calling client expects.

Decision 1: Unified /guardrail Endpoint (DECIDED)

Decision: Single unified endpointPOST /api/llm/guardrail (with the existing /cursor/hooks/validate kept as a backwards-compatible alias). The endpoint accepts events from any supported coding assistant. A thin per-client adapter normalizes the incoming schema, the shared guardrail engine evaluates the event, and a per-client serializer formats the response.
Why a single endpoint:
  • One auth/rate-limit/observability surface to operate.
  • Guardrail resolution rules are defined once and apply to every client.
  • New clients (Windsurf, Copilot, JetBrains AI, etc.) plug in by adding an adapter — no new public route.
  • Cursor and Claude Code teams point their hook config at the same URL; only the request body differs.
Still open / ambiguous:
  • Do we keep /cursor/hooks/validate mounted long-term as an alias, or deprecate it after a migration window?
  • Should /api/llm/guardrail accept a client query/header for unambiguous adapter selection, or rely purely on payload shape sniffing?
  • Is the same endpoint also the right place for non-blocking analytics events (SessionStart, Notification, etc.), or do those go to a separate /guardrail/events ingest path?

Decision 2: Claude Code Response Format

The current Cursor response format is flat:
Claude Code expects nested output:
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): New events from Claude Code that need mapping: 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?

Decision 4: Input Extraction from Claude Code Events

The current handler extracts text from the request body and wraps it as:
Claude Code sends structured tool input, not just text:
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:
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?

Decision 6: Input Mutation Support

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

All clients call the single decided endpoint — POST /api/llm/guardrail — and the gateway picks the right adapter for each.

Internal Normalized Event Format


8. Implementation Plan

Phase 1: Claude Code HTTP Hook Endpoint (Week 1-2)

  1. Create src/routers/claudeCodeHooks.ts and src/handlers/claudeCodeHooksHandler.ts
  2. Mount at /claude-code/hooks/validate and /api/llm/claude-code/hooks/validate
  3. Handle PreToolUse, PostToolUse, UserPromptSubmit events
  4. Parse Claude Code input format, map to guardrail types
  5. Return Claude Code response format (hookSpecificOutput)
  6. Auth via Bearer token header (existing JWT flow)

Phase 2: Adapter Layer Refactor (Week 2-3)

  1. Extract common guardrail resolution logic from cursorHooksHandler.ts
  2. Create src/hooks/adapters/cursor.ts and src/hooks/adapters/claudeCode.ts
  3. Create src/hooks/normalizedEvent.ts for internal format
  4. Refactor both handlers to use shared core

Phase 3: Advanced Features (Week 3-4)

  1. Input mutation support (return modified tool inputs)
  2. Additional context injection
  3. defer permission support
  4. Analytics passthrough for non-blocking events (SessionStart, Stop, etc.)

Phase 4: Distribution & Onboarding (Week 4-5)

  1. CLI bridge script for Cursor (npm package or downloadable script)
  2. Claude Code .claude/settings.json template generator
  3. TrueFoundry dashboard UI for hooks configuration
  4. Documentation and onboarding guide

9. Open Questions (Sorted by Priority)

P0 — Must answer before implementation

P1 — Should answer before Phase 2

P2 — Can defer to Phase 3+


10. Appendix: Client Configuration Examples

A. Cursor Setup (with CLI bridge)

.cursor/hooks.json:
tfy-hook-bridge script (conceptual):

B. Claude Code Setup (native HTTP)

.claude/settings.json:

11. References