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.