Reference

Hook contract

F0.4 + F0.5 + F1.x hook layer · Last updated: 2026-04-30

fastpace ships with hooks that mediate AI tool calls: JSON on stdin, JSON decision on stdout, exit 0. The scripts are written once against a canonical event model and generated into each assistant's native bundle by fastpace plugin build --target claude-code|codex|gemini-cli|all — a Claude Code plugin (PreToolUse/PostToolUse/UserPromptSubmit), a Codex plugin (same events, .codex-plugin manifest), and a Gemini CLI extension (BeforeTool/AfterTool, with matcher translation like Bash → ShellTool). One codebase, three vendors, all 15 hooks native on each — that's how fastpace stays runtime-agnostic. fastpace plugin coverage prints the hook→native-event matrix.

Hook input

Every hook reads stdin once. The JSON body has these well-known fields:

{
  "hook_event_name": "PreToolUse" | "PostToolUse" | "UserPromptSubmit" | "Mutation",
  "tool_name": "Bash" | "Read" | "Write" | "Edit" | "Grep" | "Glob" |
               "mcp__github__list_issues" | ...,
  "tool_input": { ... tool-specific ... },
  "tool_output": { ... post-call only ... },
  "metadata": {
    "agent_id": "reviewer",
    "model": "claude-opus-4.7",
    "runtime": "bedrock-claude-opus-4.7",
    "endpoint": "https://...",
    "training_opt_out": true,
    ...
  },
  "session_id": "...",
  "cwd": "/path/to/repo"
}

Hook output

{
  "decision": "allow" | "block" | "modify",
  "reason": "human-readable string",
  "summary": { ... structured signal recorded in the audit chain ... },
  "coach": "optional structured coaching message"
}

Decision values

Shipped hooks

HookPhasePurpose
audit-loggerPostToolUseF0.2 — appends every tool call to the hash-chained signed audit log.
prompt-redactorUserPromptSubmitF0.5 — strips secrets + PII before the prompt reaches the runtime. Custom patterns via config.
agent-scope-guardPreToolUseF0.4 — enforces allowed_tools / paths / commands / max_files per agent.
runtime-guardPreToolUseF1.11 — blocks calls to runtimes not on the approved list. Fires gate.failed webhook on block.
mcp-scope-mediatorPreToolUseF1.7 — checks allowed_servers + per-server allowed_actions for any mcp__* tool call.
run-manifest-writerPostToolUseF1.1 — writes a signed run-manifest receipt for every tool call.
correction-detectorPostToolUse Edit/WriteF2.1 — uses git blame to detect human edits to AI-authored lines; classifies severity; appends to corrections.log.
sast-requiredPreToolUse BashF2.13 — fires only on git push (or opt-in patterns). Runs every configured SAST/lint adapter; blocks if any finding meets block_on.

Writing your own hook

Drop a Node script into .claude/hooks/<name>.js. The runtime will pipe stdin to it and read stdout. Keep it under 50ms on the happy path — see the bench harness for measurement.

#!/usr/bin/env node
'use strict';

const chunks = [];
process.stdin.on('data', (c) => chunks.push(c));
process.stdin.on('end', () => {
  let input = {};
  try { input = JSON.parse(Buffer.concat(chunks).toString('utf8')); }
  catch { emit({ decision: 'allow', reason: 'parse_error' }); return; }

  // your decision logic here
  if (input.tool_name === 'Bash' && /rm -rf \/.*/.test(input.tool_input.command)) {
    return emit({ decision: 'block', reason: 'dangerous_command' });
  }

  emit({ decision: 'allow' });
});

function emit(o) { process.stdout.write(JSON.stringify(o) + '\n'); process.exit(0); }

Failing safely

Wrap your main path in try/catch. Return { decision: 'allow' } on unexpected errors — the alternative is the developer being unable to work, which produces worse outcomes than a missed signal. High-risk hooks (runtime-guard, agent-scope-guard) fail closed by explicit design — if the policy can't be evaluated, the call is blocked.

Firing webhooks from a hook

Use the bundled helper at assets/hooks/_webhook-fire.js. It deferred-imports @fastpace-ai/fp's webhook module and fires fully async, so the hook's allow/block decision is never delayed:

require('./_webhook-fire').fireBackground(repo, 'gate.failed', { reason: '...' });

See F3.10 webhook event bus for the seven-event taxonomy.

Privacy

Your hook can see prompt + response text in the input. Do NOT log either to disk. The shipped hooks compute sha256 digests of prompt/response bytes (when surfaced) and persist only the digests — never plaintext. This is a load-bearing invariant for the open audit schema; honor it in any hook you add.