Overview
Bridge plugin that brings Han's full plugin ecosystem to OpenAI Codex CLI â validation hooks, core guidelines, skills, and disciplines.
What This Does
Han plugins define validation hooks, specialized skills, and agent disciplines that run during Claude Code sessions. This bridge makes the entire ecosystem work in Codex CLI:
- Hooks â PreToolUse, PostToolUse, and Stop validation (biome, eslint, tsc, etc.) with parallel execution
- Guidelines â Core principles (professional honesty, no excuses, skill selection) injected on session start
- Datetime â Current time injected on every user prompt
- PreToolUse blocking â JSON permission decisions deny tool execution per Codex convention
- Events â Unified JSONL logging with
harness: "codex"for Browse UI visibility
Coverage Matrix
| Claude Code Hook | Codex Hook | Status |
|---|---|---|
| SessionStart | SessionStart | Implemented |
| UserPromptSubmit | UserPromptSubmit | Implemented |
| PreToolUse | PreToolUse | Implemented |
| â | PermissionRequest | Implemented (runs PreToolUse hooks) |
| PostToolUse | PostToolUse | Implemented |
| Stop | Stop | Implemented |
| SubagentStop | SubagentStop | Implemented (runs Stop hooks) |
| SubagentStart | SubagentStart | Available (no-op) |
| PreCompact | PreCompact | Available (no-op) |
| PostCompact | PostCompact | Available (no-op) |
Key Difference from OpenCode Bridge
The OpenCode bridge is an in-process JS plugin that hooks into OpenCode's runtime. The Codex bridge is a CLI tool that Codex hooks call as shell commands.
This is because Codex's hook system is shell-based (like Claude Code), not plugin-based (like OpenCode). Each Codex hook calls npx -y codex-plugin-han <event>, which:
- Reads JSON from stdin (Codex's hook payload)
- Maps Codex tool names to Claude Code tool names
- Discovers and executes matching Han hooks
- Outputs a JSON decision to stdout (all logging goes to stderr)
Validation Flow
PreToolUse (Pre-Execution Gate)
Runs before a tool executes via Codex's PreToolUse hook:
Agent about to run a tool
-> Codex calls: npx -y codex-plugin-han pre-tool-use
-> Bridge reads stdin JSON { tool_name: "apply_patch", ... }
-> Maps apply_patch -> Edit (Claude Code name)
-> Matches PreToolUse hooks by tool name
-> Runs matching hooks in parallel
-> All pass: {} (allow)
-> Any fail: hookSpecificOutput.permissionDecision = "deny"
(reason sent to the agent)
PostToolUse (Primary Path - In-the-Loop)
The most important hook type. When the agent edits a file:
Agent edits src/app.ts via apply_patch
-> Codex calls: npx -y codex-plugin-han post-tool-use
-> Bridge extracts file paths (file_path fields + patch headers)
-> Matches PostToolUse hooks (biome lint-async, eslint lint-async, etc.)
-> Runs all matching hooks in parallel
-> All pass: {}
-> Any fail: { "decision": "block", "reason": ... }
(reason replaces the tool result as agent feedback)
Stop / SubagentStop (Full Project)
When the agent finishes a turn:
Agent signals completion
-> Codex calls: npx -y codex-plugin-han stop
-> Bridge runs Stop hooks (full project lint, typecheck, tests)
-> All pass: {}
-> Any fail: { "decision": "block", "reason": ... }
(Codex turns the reason into a new user prompt;
the agent keeps working until validation passes)
Context Injection
SessionStart
Core guidelines are injected via hookSpecificOutput.additionalContext:
- Professional honesty (verify before accepting claims)
- No time estimates (use phase numbers)
- No excuses policy (Boy Scout Rule)
- Date handling best practices
- Mandatory skill selection
If HAN_DISCIPLINE is set in the environment, that discipline's persona
context is injected alongside the guidelines.
UserPromptSubmit
Current local datetime is injected on every user prompt via additionalContext, so the LLM always knows the current time.
Setup
Prerequisites
Han plugins must be installed:
curl -fsSL https://han.guru/install.sh | bash
han plugin install --auto
Install the Bridge
han plugin install codex@han
Enable Codex Hooks
Hooks are gated behind a feature flag. Add to ~/.codex/config.toml:
[features]
hooks = true
Wire the Hook Events
Add to ~/.codex/hooks.json (global) or <repo>/.codex/hooks.json (per project). Timeouts are in seconds:
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume|clear|compact",
"hooks": [
{ "type": "command", "command": "npx -y codex-plugin-han session-start", "timeout": 30 }
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{ "type": "command", "command": "npx -y codex-plugin-han user-prompt-submit", "timeout": 10 }
]
}
],
"PreToolUse": [
{
"matcher": "Bash|apply_patch|Edit|Write|spawn_agent|Agent",
"hooks": [
{ "type": "command", "command": "npx -y codex-plugin-han pre-tool-use", "timeout": 30 }
]
}
],
"PermissionRequest": [
{
"hooks": [
{ "type": "command", "command": "npx -y codex-plugin-han permission-request", "timeout": 15 }
]
}
],
"PostToolUse": [
{
"matcher": "apply_patch|Edit|Write",
"hooks": [
{ "type": "command", "command": "npx -y codex-plugin-han post-tool-use", "timeout": 120 }
]
}
],
"PreCompact": [
{
"matcher": "manual|auto",
"hooks": [
{ "type": "command", "command": "npx -y codex-plugin-han pre-compact", "timeout": 10 }
]
}
],
"PostCompact": [
{
"hooks": [
{ "type": "command", "command": "npx -y codex-plugin-han post-compact", "timeout": 10 }
]
}
],
"SubagentStart": [
{
"hooks": [
{ "type": "command", "command": "npx -y codex-plugin-han subagent-start", "timeout": 10 }
]
}
],
"SubagentStop": [
{
"hooks": [
{ "type": "command", "command": "npx -y codex-plugin-han subagent-stop", "timeout": 180 }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "npx -y codex-plugin-han stop", "timeout": 180 }
]
}
]
}
}
MCP Server (Complementary)
Hooks handle validation and context injection. For Han's MCP tools
(memory, codebase analysis, hook utilities), add the Han MCP server to
~/.codex/config.toml:
[mcp_servers.han]
command = "han"
args = ["mcp"]
AGENTS.md Integration
For core guidelines without the full bridge, add to your project's AGENTS.md:
## Han Guidelines
- Verify claims before accepting them (read code, search codebase, run tests)
- Never provide time estimates
- Own every issue you encounter (Boy Scout Rule)
- Use ISO 8601 for timestamps, store in UTC
Architecture
Codex CLI Runtime
|
|-- SessionStart ââââââââââââââââââââââââââââ> additionalContext
| (startup/resume/clear/compact) (core guidelines + discipline)
| |
| ââ han coordinator ensure âââââââââââ
| (Browse UI visibility)
|
|-- UserPromptSubmit ââââââââââââââââââââââââ> additionalContext
| (every user prompt) (current datetime)
|
|-- PreToolUse ââââââââââââââââââââââââââââââ> PreToolUse hooks
| (Bash, apply_patch, spawn_agent) (validation gates)
| |
| ââ hookSpecificOutput âââââââââââââââ
| (permissionDecision: deny)
|
|-- PermissionRequest âââââââââââââââââââââââ> PreToolUse hooks
| (Codex about to prompt user) |
| |
| ââ hookSpecificOutput âââââââââââââââ
| (decision.behavior: deny)
|
|-- PostToolUse âââââââââââââââââââââââââââââ> PostToolUse hooks
| (apply_patch/Edit/Write) (biome, eslint, tsc)
| |
| ââ decision: "block" ââââââââââââââââ
| (reason = tool result feedback)
|
|-- Stop / SubagentStop âââââââââââââââââââââ> Stop hooks
| (agent finished turn) (full project lint)
| |
| ââ decision: "block" ââââââââââââââââ
| (reason = new user prompt)
|
|-- JSONL logger ââââââââââââââââââââââââââââ> Event logging
(hook_run, hook_result, hook_file_change) (Browse UI visibility)
Bridge Internals:
context.ts Core guidelines + datetime injection
discovery.ts Read settings + marketplace + resolve plugin paths
skills.ts Discover SKILL.md files from installed plugins
disciplines.ts Discover discipline plugins, persona context builder
matcher.ts Filter by tool name, file globs, dirsWith
executor.ts Spawn hook commands as parallel promises
formatter.ts Structure results as Codex JSON decisions
events.ts JSONL event logger (harness="codex")
cache.ts Content-hash caching (SHA-256)
Tool Name Mapping
Codex CLI uses different tool names than Claude Code. The bridge maps them before matching Han hooks:
| Codex Tool | Claude Code Equivalent |
|---|---|
Bash | Bash |
apply_patch | Edit |
Edit / Write (apply_patch aliases) | passed through as-is |
spawn_agent | Agent |
mcp__server__tool | passed through as-is |
Event Logging & Harness Attribution
The bridge writes Han-format JSONL events to ~/.han/codex/projects/{slug}/{sessionId}-han.jsonl. Each event includes harness: "codex" so the coordinator can distinguish Codex sessions from Claude Code and OpenCode sessions.
Events logged:
hook_run/hook_resultâ Hook execution lifecycle (start, success/failure, duration)hook_file_changeâ File edits detected viaPostToolUse
The bridge sets HAN_PROVIDER=codex in the environment for child processes and starts the Han coordinator in the background on SessionStart with han coordinator ensure --background. The coordinator discovers ~/.han/<harness>/projects on disk, watches it, and indexes each <sessionId>-han.jsonl into SQLite as a session attributed to that harness, which is how Codex sessions reach the Browse UI. There is no registration step and no --watch-path flag. The coordinator enumerates those roots when it starts, so the very first session from a newly installed bridge is picked up at the next coordinator start or scan rather than live. Every session after that is live.
Remaining Gaps
- SubagentStart context: Codex supports
additionalContexton SubagentStart, but the bridge has no per-session discipline state to inject (one process per event) - Checkpoints: Session-scoped checkpoint filtering is not yet implemented
Development
# From the han repo root
cd plugins/bridges/codex
# The plugin uses TypeScript directly (Bun handles it)
# No build step required
# Smoke test: every event reads stdin until EOF and prints JSON
bun src/index.ts stop < /dev/null
echo '{"tool_name":"apply_patch","tool_input":{"patch":"*** Begin Patch\n*** Update File: src/x.ts\n*** End Patch"}}' | bun src/index.ts post-tool-use
License
Apache-2.0