Skip to main content

Synced verbatim from fornax-core at build time — edit it there, not here.

Adapter capability matrix — Claude Code vs. Codex CLI

Status: living doc, empirically grounded. Re-verify hook schemas against the installed CLI version before an adapter hard-codes field names — both surfaces are actively changing.

Claude Code

Source: this machine's ~/.claude/settings.json + ~/.claude/statusline.py + ~/.claude/hooks/bg-track.py (read directly, 2026-08-28).

Hooks are on by default once configured in settings.json. Every hook payload is JSON on stdin with common fields session_id, transcript_path, cwd, hook_event_name, plus event-specific fields.

EventEvent-specific fieldsEvidence obtainable
PreToolUsetool_name, tool_inputExact tool name + args before execution; can block/rewrite input
PostToolUsetool_name, tool_input, tool_responseProvider-serialized result (exit-code-ish data for Bash) — CC's own summarized view, may be truncated; no independent raw stdout capture
SessionStartsource (startup/resume/clear/compact)Session lifecycle only, no tool evidence
UserPromptSubmitpromptRaw user prompt text
SubagentStart/SubagentStopagent_id, agent_typeExact subagent lifecycle
Stop(turn end)Available but not wired in this machine's config — must add a matcher to observe agent-turn completion
Notification, PreCompactAvailable per docs but unconfirmed payload shape here; not wired in this config

Known gaps even with hooks fully wired: no hook fires when a backgrounded shell/Monitor/Workflow tool call actually completes; no raw unbuffered stdout tap independent of tool_response; no PreToolUse/PostToolUse for Edit/Write/ Read/WebFetch/Task unless a matcher is added.

Codex CLI

Source: openai/codex docs/config.md/docs/hooks.md, GitHub issues #4005/#21148/#25141/#18491/#21660/#24948, PR #19905 (research pass, not primary docs read directly — re-verify against the actually-installed Codex version).

CapabilityCodex CLINotes
Hooks (PreToolUse/PostToolUse/Stop/SessionStart/SessionEnd/SubagentStart/SubagentStop/PermissionRequest/UserPromptSubmit/PreCompact/PostCompact/Interrupt)Exists, JSON on stdinOpt-in: requires [features].codex_hooks = true in ~/.codex/config.toml; Stage::UnderDevelopment; schema actively changing; org admin can force allow_managed_hooks_only = true in requirements.toml to lock it out entirely
PreToolUse blockingPartialBash/shell + reportedly apply_patch/MCP; Read-style ops reportedly don't fire it; no updatedInput rewrite support (rejected at runtime, open FR #18491)
Legacy notify/notify_commandYes, user-config only (~/.codex/config.toml, project-local ignored)Single coarse agent-turn-complete event, JSON-string arg (type, thread-id, turn-id, cwd, client, input-messages, last-assistant-message); being deprecated in favor of Stop hook — don't build new integrations on it
On-disk session transcriptYes — "rollout" files, ~/.codex/sessions/YYYY/MM/DD/rollout-<timestamp>-<uuid>.jsonlAlways-on (not feature-flagged), JSONL, one RolloutLine ({type, timestamp, payload:RolloutItem}) per line — user msg / assistant reply / shell invocation / patch proposal / sandbox response. World-readable (mode 0644, issue #21660). Can grow 700MB–2GB (issue #24948) — a tailing reader must handle rotation/size. This is the primary integration point, not hooks.
Tool-call args/results (cmd, exit code, diffs)Observable via rollout JSONL (and hooks if enabled)Exact field names (e.g. an exit_code key) not confirmed against a live file in this pass — must dump a real rollout-*.jsonl and confirm field names empirically before FORNX-29 codes the parser

Explicit UNAVAILABLE / capability gaps for Codex (report, never infer around)

  • Universal tool-call interception with input rewriting.
  • A stable, versioned public hook JSON Schema shipped with a release.
  • Hooks enabled out-of-the-box (requires opt-in feature flag).
  • Guaranteed non-suppressible hook execution (admin can disable via requirements.toml).

Adapter design consequence (FORNX-24 / FORNX-28 / FORNX-29)

  • fornax-adapter-claude: a hook command wired to PreToolUse/PostToolUse/ Stop/SessionStart/UserPromptSubmit/SubagentStart/SubagentStop (add Stop — not currently wired), reading stdin JSON, normalizing to AgentEvent, writing to the daemon over the Unix Domain Socket.
  • fornax-adapter-codex: primary path is a rollout-file tailer, not hooks — hooks are opt-in/unstable and can be admin-disabled, so a Codex integration that only used hooks could go completely dark. Tail the active session's rollout-*.jsonl, normalize known RolloutItem variants to AgentEvent, and mark RuntimeCapabilities.supports_pre_tool_use / supports_post_tool_use / supports_session_stop_event per what the installed Codex version actually proves out (feature-flag hooks as a secondary, best-effort input if codex_hooks happens to be on).
  • Before writing the Codex rollout parser: capture one real rollout-*.jsonl from an actual Codex session on this machine and confirm field names — do not hard-code shapes from secondary sources.

Confirmed rollout JSONL schema (empirical, 2026-08-28)

Verified against real local rollout files (field names only — no payload values reproduced here; some historical rollout files were found to contain plaintext secrets in captured command output, tracked as a non-blocking finding on FORNX-33, not reproduced in this doc).

Top-level line shape: {"type": <str>, "payload": {...}} (plus timestamp present on most lines). Observed top-level type values: session_meta, turn_context, response_item, event_msg, compacted, world_state.

Shell/tool execution evidence lives under type: "event_msg" with payload.type one of exec_command_begin / exec_command_end (and task_started / task_complete / user_message / token_count for turn-level bookkeeping). Confirmed exec_command_end payload fields:

{
"type": "exec_command_end",
"call_id": "<string>",
"turn_id": "<string>",
"command": ["<argv...>"],
"cwd": "<path>",
"aggregated_output": "<string, stdout+stderr merged>",
"exit_code": <int>,
"duration": { "secs": <int>, "nanos": <int> },
"status": "completed" | "failed",
"formatted_output": "<string>",
"source": "<string, e.g. user_shell | unified_exec_startup>"
}

This is the exact field Fornax needs for the epic's canonical aha scenario: exit_code + command + aggregated_output from exec_command_end maps directly to EvidenceKind::ExitCode / EvidenceKind::ToolResult with provenance codex:rollout:exec_command_end. aggregated_output is command stdout+stderr merged — treat as sensitive-by-default for the privacy classifier (FORNX-33), same as Claude Code's tool_response.