When I launched fscars in May, it wired into one agent. The architecture diagram said “one core, many adapters,” but there was a single adapter — Claude Code — and a roadmap line for the rest.
fscars 0.4.0 closes that gap for OpenAI Codex. A correction you write once now fires in both agents, and the interesting part is what doesn’t change: the scar. The code that catches a mistake is identical on both sides. Only the thin layer that speaks each agent’s hook protocol differs.
pip install fscars # 0.4.0
fscar init # wires Claude Code
fscar init --adapter codex # wires Codex native hooks
What a scar is, in one paragraph
A functional scar is enforcement code that runs outside the model. You take a correction you keep repeating — “use encoding='utf-8' when you read that CSV,” “don’t ship a 400-line file without a self-review pass” — and you express it as a small rule that fires deterministically the next time the conditions for the original mistake show up. The model does not get to decide whether to apply it. That is the whole point: the layers below this one (system prompt, knowledge base, memory) all present information and leave the model as the decider. A scar removes the choice.
For that to work, the rule has to be wired into the agent’s hook system. Every agent has a slightly different one. The adapter is the translator.
How it works with Claude Code
fscar init writes one line into .claude/settings.json — the single command python -m fscars.run_hook — and registers it for every event Claude Code emits: session start, prompt submit, pre-tool, post-tool, stop.
When Claude Code is about to use a tool, it hands the hook a JSON payload on stdin: which tool, what arguments, the working directory. The fscars engine reads it, asks every registered scar “does this match you?”, runs the ones that say yes, logs each fire, and writes back the response Claude Code expects. If a scar decides the action should be blocked, the hook exits with code 2 and the tool call is denied before it runs. A warning instead of a block injects context the model reads on its next step.
One command, every event, no per-scar scripts. The engine does the dispatching.
How it works with Codex
Until last week, the Codex adapter could not do the same thing, for an honest reason: Codex did not expose a stable hook API, so the installer worked in instruction mode. It wrote an AGENTS.md block telling Codex to consult fscars as a preflight and audit step. Useful, but it left the model as the decider — exactly the layer a scar is supposed to escape.
OpenAI shipped a native hook API for Codex. The shape turned out to be close to Claude Code’s: a config file (.codex/hooks.json) that maps each event to a command, with the same idea of a single entrypoint receiving every event. So fscar init --adapter codex now registers the same python -m fscars.run_hook command natively, for the same five events.
The part that matters: on PreToolUse, a Codex scar can deny a Bash command, a file edit (apply_patch), or an MCP call before it executes, by returning permissionDecision: "deny". That is real blocking, not a note in the model’s context. The instruction block stays behind as a fallback and an audit contract, but it is no longer the mechanism.
Two small things the adapter handles so your scars don’t have to care which agent they are running under:
- Codex edits files through
apply_patch, notWrite/Edit. The adapter normalizes that, and pulls the touched file path out of the patch, so a scar written againstWrite/Editfires unchanged on a Codex edit. - Codex requires you to trust a hook once. After
fscar init --adapter codex, run/hooksin the Codex CLI and approve it. Codex will not run a non-managed command hook until you do — which is the right default for a tool that can deny actions.
The honest limitation
Codex’s pre-tool interception is a guardrail, not a complete boundary. The documentation is explicit that it does not yet intercept every shell path, and it does not touch non-shell, non-MCP tools at all — a web search, for instance, runs without passing through the hook. So on Codex, a scar that depends on catching every possible route to a mistake cannot promise that yet. A scar that catches the common routes — the Bash command, the file edit, the MCP call — does.
Claude Code’s surface is wider today. I am not going to paper over that. The badge on the repo says Codex · native hooks and the README says guardrail, not boundary, in the same breath. If that changes upstream, the adapter is where it changes, and your scars stay where they are.
A footnote on who built this
The original instruction-mode adapter was written by Codex itself, working in its own sandbox against the repo. The native-hooks version was implemented by Claude Code in my workspace, then reviewed out of band by Codex, which approved it and flagged one detail worth keeping. I operate both agents, and the repo is the source of truth that keeps them honest with each other.
The mechanics are worth stating plainly: the coding agents are helping build the tool that constrains them. That is only safe because the constraints are real. Nothing merges without passing the gate, nothing ships to PyPI without me approving the release, and the scars catch the regressions in between. I delegate the implementation; I keep the gate.
If you want to run it
pip install fscars
fscar init --adapter codex # or omit --adapter for Claude Code
fscar list # 5 starter scars come pre-installed
The launch post, Functional Scars — turning corrections into a primitive, covers the why and the four-layer model. The paper explains the framework. If you already run scars in Claude Code, fscar init --adapter codex is the only new command you need; the scars come with you.