Skip to main content
Claude Code routes model traffic through the AI Gateway via ANTHROPIC_BASE_URL, but coding assistants also perform work the model API never sees — running shell commands, editing files, and calling MCP tools. TrueFoundry governs that surface through Claude Code hooks: each hook payload is sent to a dedicated gateway endpoint that runs your configured guardrails and returns Claude Code’s native hook response, so a violation blocks the action (or rewrites it) directly in the client.
This is complementary to the standard Claude Code integration. That covers model requests (cost, routing, LLM guardrails on completions); this page covers prompt submissions and tool calls via hooks. You can use either or both.

How it works

  1. Claude Code fires a hook (e.g. before a tool runs) and sends the event payload on stdin.
  2. A hook command POSTs that payload to the AI Gateway endpoint POST /hooks/claude-code.
  3. The AI Gateway runs the guardrails you nominated for that hook and returns a JSON response containing Claude Code’s native hook fields (decision, permissionDecision, updatedInput, updatedToolOutput).
  4. Claude Code acts on that response — allowing, blocking, or applying the rewritten content.

What you can and can’t do

There are two things a guardrail can do at a hook:
  • Validation — decide block or allow. A block stops the prompt or tool call (or, for PostToolUse, replaces the already-produced result — see caveats).
  • Mutationrewrite the content in place (e.g. redact an email from a tool’s input or output) and let the action continue with the sanitized version.
The three supported hook events differ in which of these they support: In short:
  • Validation works on all three events.
  • Mutation works only on the MCP tool pathsPreToolUse (tool input) and PostToolUse (tool result). The prompt path (UserPromptSubmit) is validation-only.
There is no LLM-output direction here: Claude Code has no hook for the assistant’s generated response. To guardrail model responses, use output guardrails on the model request path (see Guardrails).

Choosing which guardrails run

Guardrails for the hook endpoint are selected per request via the x-tfy-hook-guardrails header, a flat JSON array of guardrail selectors. The endpoint does not read tenant Guardrail Config rules; the caller nominates the selectors.
This is a dedicated header for the hook endpoint. It is not the object-shaped x-tfy-guardrails header used on the model request path, and the two are not interchangeable — sending x-tfy-guardrails here nominates nothing and your guardrails will not run.
No placement keys are needed because the hook event itself determines which pipeline the selectors run through: Each selector is group/guardrail-name. Because every hook event has its own hook config, you nominate different guardrails per event by giving each command its own array — as the example below does. Two behaviours to know:
  • Header absent — no guardrails are nominated and the request passes through.
  • Header present but malformed (not a JSON array of strings) — fail closed: the request is blocked.

Prerequisites

  1. A guardrail group and at least one guardrail integration — see Create a guardrail. Note the selector string, e.g. security/email-regex.
  2. A TrueFoundry API key with access to that guardrail group.
  3. Claude Code installed and (optionally) already pointed at the AI Gateway per the Claude Code integration.

Configure the hooks

Add the hooks to your project’s .claude/settings.json (or ~/.claude/settings.json). This example wires all three events to the endpoint and blocks anything the security/email-regex guardrail flags. Replace the selector with your own.
TFY_GUARDRAIL_URL is your gateway host root — the same host as ANTHROPIC_BASE_URL but without the /api/llm suffix (the hook endpoint is mounted at the root, e.g. https://your-gateway-host/hooks/claude-code).
Claude Code also supports native "type": "http" hooks. If you use those, put x-tfy-api-key and x-tfy-hook-guardrails in the hook’s headers, reference secrets as $TFY_API_KEY, and list them in allowedEnvVars so they are interpolated from the environment.

Verify

Send a payload through the endpoint directly before wiring it into Claude Code:
A blocked request returns decision: "block" (and, for PreToolUse, hookSpecificOutput.permissionDecision: "deny"). A request that passed its guardrails returns {"verdict": true, "transformed": false}.
If the response contains "message": "No guardrail configured for this request.", then no guardrail ran — the request was not checked. This is what you get when the header name is wrong or its value isn’t a JSON array, so verify you are sending x-tfy-hook-guardrails with a flat array like ["security/email-regex"]. A verdict of true on its own does not mean a guardrail approved the request.

Response contract

  • verdict: false — the guardrail blocked. The response carries Claude Code’s native block fields for the event (decision: "block", plus permissionDecision: "deny" for PreToolUse; for PostToolUse the flagged result is replaced with the guardrail message via updatedToolOutput).
  • transformed: true — a mutating guardrail rewrote the content. The rewrite is applied through the native field for the event (updatedInput for PreToolUse, updatedToolOutput for PostToolUse).
  • message present with verdict: true — no guardrail was nominated for this request, so nothing was checked. The action proceeds. Treat this as a configuration problem, not a pass: see the warning under Verify.
  • Fail-closed — if the AI Gateway hits an error (an internal failure, or a misconfigured/unresolvable guardrail selector) it returns verdict: false with the event’s native block fields, so a request it couldn’t verify is blocked rather than let through.

Caveats

  • PostToolUse cannot prevent a tool from executing. It fires after the tool has run, so its side effects (a file written, a command executed, an API called) have already happened. Blocking at PostToolUse only replaces the result the model sees — it does not undo the action. To actually stop a dangerous tool call, use PreToolUse.
  • UserPromptSubmit is validation-only. You can block a prompt, but you cannot redact or rewrite it — Claude Code has no mechanism to substitute a modified prompt. If a prompt violates policy, it’s blocked, not sanitized.
  • No LLM-output guardrails via hooks. Claude Code exposes no hook for the assistant’s generated response, so model output can’t be scanned here. Use output guardrails on the model request path instead (Guardrails).
  • PreToolUse mutation auto-allows the call. When a guardrail rewrites the tool input, the response sets permissionDecision: "allow" alongside updatedInput, so the sanitized call proceeds without the usual permission prompt. This is intentional (the guardrail already vetted it), but it does bypass interactive approval for that call.
  • Selection is header-driven, not rule-based. The endpoint runs exactly the guardrails named in the x-tfy-hook-guardrails header; it does not consult tenant Guardrail Config rules. Each Claude Code hook config carries its own selector list.
  • Fail-closed on errors. If the AI Gateway errors internally — including when a nominated guardrail selector can’t be resolved (unknown name, invalid placement, unsupported type) — it returns verdict: false and blocks the action, so a request that couldn’t be verified never slips through. This only applies when the AI Gateway is actually reached: if it’s unreachable and curl returns nothing, Claude Code has no decision to act on and the action proceeds (see the next point) — so hooks still shouldn’t be your only control for hard-security requirements.
  • Command hooks depend on curl output. For "type": "command" hooks, Claude Code reads the guardrail decision from the command’s stdout (curl … -d @-). A network failure, wrong URL, or missing x-tfy-api-key yields no decision and the action proceeds.
  • matcher: "*" on the tool hooks runs the guardrail on every tool. Narrow it (e.g. "Bash", "Edit|Write", or a specific MCP tool) if you only want to guard certain tools.