Skip to main content

Claude Code integration

fornax-hook-claude is a thin adapter: it reads a Claude Code hook's JSON payload from stdin, translates it into Fornax's canonical event format, and forwards it to the local daemon over a Unix Domain Socket. It contains no verification logic itself (adapters stay thin by design — the same rule applies to the Codex adapter).

Wire it into Claude Code

Add to ~/.claude/settings.json (global) or .claude/settings.local.json (project-scoped — see Installation):

{
"hooks": {
"PreToolUse": [{ "hooks": [{ "type": "command", "command": "fornax-hook-claude" }] }],
"PostToolUse": [{ "hooks": [{ "type": "command", "command": "fornax-hook-claude" }] }],
"Stop": [{ "hooks": [{ "type": "command", "command": "fornax-hook-claude" }] }],
"SessionStart": [{ "hooks": [{ "type": "command", "command": "fornax-hook-claude" }] }]
}
}

If a project-local hooks object already exists (e.g. other tools' hooks), add these entries alongside them under the same event keys — Claude Code does not guarantee that project-level hooks merge with global ones, so treat it as a full replace and copy every existing entry forward too.

What Fornax actually observes

Confirmed empirically against a live Claude Code v2.1.238 session (not assumed from docs) — see the capability matrix under Reference in the sidebar for the full, living detail:

EventWhat it gives Fornax
PreToolUseExact tool name + args, before execution.
PostToolUseThe tool's result as Claude Code itself summarizes it — for Bash, this is {stdout, stderr, interrupted, isImage, noOutputExpected}, not a literal exit code.
SessionStartSession lifecycle only — no tool evidence.

Known gap: no hook fires when a backgrounded shell/Monitor/Workflow tool call actually completes, and there's no raw unbuffered stdout tap independent of tool_response. When evidence a verifier needs isn't observable, the finding is UNAVAILABLE — reported, never guessed around. See Claims, Evidence & Findings.

A real bug this surfaced (and fixed)

Because tool_response for Bash has no exit_code field, an earlier version of this adapter guessed at field names (exit_code, exitCode, returncode, status) that never actually appear. The fix uses a documented heuristic instead (stderr non-empty / interrupted / stdout present with empty stderr) and marks the resulting evidence as heuristic-derived, so downstream consumers know it isn't a literal exit code. See the First finding walkthrough for the full incident this came from.

Isolating the integration to one project

If you don't want Fornax's hooks active in every Claude Code session, use .claude/settings.local.json in one repo only — see Self-host / local development for fornax-core's own dogfooding setup, including how it keeps this isolated from your global config and from other repos.