Automatic enforcement of APEX methodology and SOLID principles through Codex hooks.
The hooks system ensures that agents:
- Consult skills before writing code
- Follow APEX methodology (Analyze → Plan → Execute → eXamine)
- Respect SOLID principles (files < 100 lines, interfaces separated)
~/.codex/config.toml ← [features] hooks=true (canonical 0.129+, codex_hooks legacy alias)
│
▼
~/.codex/plugins/cache/fusengine-plugins/<plugin>/local/hooks.json
│ ← chemins absolus réécrits par l'installer
▼
python3 ./scripts/<event>/<guard>.py ← exécuté directement par Codex
$CLAUDE_HOME/settings.json
│
▼
scripts/hooks-loader.ts ← Bun + SOLID, exécution parallèle
│
▼
plugins/*/hooks/hooks.json
~/.codex/.tmp/marketplaces/fusengine-plugins/setup.sh~/.codex/.tmp/marketplaces/fusengine-plugins/setup.ps1L'installer écrit :
hooks.jsonpar plugin dans~/.codex/plugins/cache/fusengine-plugins/<plugin>/local/(chemins absolus)_shared/mirroré dans~/.codex/plugins/cache/fusengine-plugins/<plugin>/_shared/[features] hooks=true(canonical 0.129+, PR openai/codex#20522) + 9 flags audités 0.130 (tool_search,personality,multi_agent,fast_mode,shell_snapshot,enable_request_compression,skill_mcp_dependency_install,memories,goals) dans~/.codex/config.toml. Removed in 0.129+ (no-op):undo,steer. UnderDevelopment laissés aux défauts Codex :chronicle,enable_fanout,child_agents_md,plugin_hooks- API keys (prompts interactifs)
- Shell config (bash/zsh/fish/PowerShell)
- MCP servers (sélection interactive)
- Statusline TUI Codex
All plugin hooks are automatically detected and loaded.
| Hook | Trigger | Purpose |
|---|---|---|
| SessionStart | Session starts | Load context, inject AGENTS.md, cleanup states, spawn transcript-watcher |
| UserPromptSubmit | User sends message | Detect project type, inject APEX instruction |
| PreToolUse | Before Bash/apply_patch (Write|Edit) | Block secrets (.codexignore), validate git/install, enforce APEX/SOLID phases |
| PostToolUse | After Bash/apply_patch/MCP | Validate SOLID, track changes, cache MCP results, sync transcript events |
| Stop | Codex finishes responding | Cross-platform TTS completion sound |
| PermissionRequest | Permission dialog shown | Play sound for permission prompts |
Codex 0.128 ne reconnaît que ces matchers :
Bashapply_patch(alias pourWrite/Edit)mcp__<server>__<tool>
Tout autre matcher (Read, web_search, spawn_agent, Task, Notification, SubagentStart, etc.) est silencieusement ignoré.
Codex émet plusieurs appels que les hooks ne peuvent pas intercepter directement : read_file, view_image, web_search, imagegen, spawn_agent, tool_search_call.
Solution : un watcher background (plugins/core-guards/scripts/transcript-watcher.py) lancé au SessionStart via double-fork POSIX. Il tail le rollout JSONL (~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl), extrait les events normalisés (function_call, tool_search_call, web_search_call), les classifie (explore vs research) et les expose via un buffer lu par sync-transcript-to-session.py (PostToolUse) qui les pousse dans le session-state APEX.
SessionStart → transcript-watcher.py (background, double-fork)
↓
tail ~/.codex/sessions/.../rollout-*.jsonl
↓
extract events → classify → buffer
PostToolUse (Bash|apply_patch) → sync-transcript-to-session.py
↓
read buffer → upsert into session-state.json
↓
consumed by require-apex-agents.py guard
Conservés dans plugins/<plugin>/codex-unsupported-hooks.json pour traçabilité et future compatibilité :
SessionEnd,SubagentStart,SubagentStopNotification,PreCompact,PostToolUseFailureTaskCompleted,TeammateIdle,InstructionsLoaded- Stop blocks
"type": "prompt"(Claude Code only)
| Plugin | PreToolUse | PostToolUse | UserPromptSubmit | SessionStart | SubagentStart | SubagentStop | Stop | Notification | PreCompact | SessionEnd | Setup |
|---|---|---|---|---|---|---|---|---|---|---|---|
| ai-pilot | APEX phases, APEX context | SOLID check, Doc tracking, Task sync | APEX injection | - | Context inject, Explore cache, Doc cache inject, Lessons inject, Test cache inject | Sniper lessons, Test results, Doc from transcript, SOLID from transcript | - | - | - | Analytics save | - |
| core-guards | Git, Install, Security, Pre-commit, Interfaces, File size, SOLID read | File size, Session tracking, Doc reads, SOLID reads, TS validation | AGENTS.md injection | Context, Cleanup | - | Memory | Sound | Sounds | APEX state | Cleanup, Stats | API keys |
| react-expert | Block without skill | React SOLID validation | - | - | - | - | - | - | - | - | - |
| nextjs-expert | Block without skill | Next.js SOLID validation | - | - | - | - | - | - | - | - | - |
| laravel-expert | Block without skill | Laravel SOLID validation | - | - | - | - | - | - | - | - | - |
| swift-apple-expert | Block without skill | Swift SOLID validation | - | - | - | - | - | - | - | - | - |
| tailwindcss | Block without skill | Tailwind best practices | - | - | - | - | - | - | - | - | - |
| design-expert | Block without skill | Accessibility check | - | - | - | - | - | - | - | - | - |
Sous Codex CLI 0.128+, le loader Bun n'est pas utilisé : Codex exécute directement les hooks listés dans
~/.codex/plugins/cache/fusengine-plugins/<plugin>/local/hooks.json. La structure ci-dessous concerne uniquement Claude Code.
scripts/
├── hooks-loader.ts ← Entry point
├── install-hooks.ts ← Installation + API keys
├── package.json
├── src/
│ ├── interfaces/
│ │ ├── hooks.ts ← Hook interfaces
│ │ └── env.ts ← EnvKey interface
│ ├── config/
│ │ └── api-keys.ts ← API keys configuration
│ ├── services/
│ │ ├── plugin-scanner.ts ← Scan plugins, extract hooks
│ │ ├── hook-executor.ts ← Execute hooks in PARALLEL
│ │ ├── settings-manager.ts← Manage settings.json
│ │ └── env-manager.ts ← API keys & shell config
│ └── __tests__/
│ ├── api-keys.test.ts
│ ├── env-manager.test.ts
│ ├── fs-helpers.test.ts
│ ├── hook-executor.test.ts
│ ├── install-hooks.test.ts
│ ├── plugin-scanner.test.ts
│ └── settings-manager.test.ts
└── env-shell/
├── codex-env.bash
├── codex-env.zsh
├── codex-env.fish
└── codex-env.ps1
bun test # Run 81 tests
bun test --watch # Watch mode| Version | Execution | Speed |
|---|---|---|
| v1.0 (bash+jq) | Sequential | 280ms |
| v2.0 (Bun) | Parallel | 100ms |
- Create the hooks directory:
mkdir -p plugins/my-plugin/hooks plugins/my-plugin/scripts- Create
hooks/hooks.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash ./scripts/my-check.sh"
}
]
}
]
}
}-
Create your script in
scripts/my-check.sh -
Make it executable:
chmod +x plugins/my-plugin/scripts/*.shThe hooks loader will automatically detect and load your new hooks.
Les hooks reçoivent un JSON via stdin :
{
"hook_event_name": "PreToolUse",
"session_id": "...",
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.tsx",
"content": "..."
},
"cwd": "/path/to/project"
}exit 0print(json.dumps({"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "raison du blocage"
}}))
sys.exit(0)print(json.dumps({"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "contexte à injecter"
}}))
sys.exit(0)Note : Codex n'utilise pas
exit 2ni stderr — il faut utiliserhookSpecificOutput.permissionDecisionpour bloquer/autoriser.
# Vérifier que les hooks plugin sont en place avec chemins absolus
ls ~/.codex/plugins/cache/fusengine-plugins/*/local/hooks.json
grep -l "PreToolUse" ~/.codex/plugins/cache/fusengine-plugins/*/local/hooks.json
# Vérifier les feature flags Codex
grep -E "^hooks|^tool_search|^multi_agent" ~/.codex/config.toml
# Re-run installation (macOS/Linux)
~/.codex/.tmp/marketplaces/fusengine-plugins/setup.sh
# Re-run installation (Windows PowerShell)
~/.codex/.tmp/marketplaces/fusengine-plugins/setup.ps1Test direct d'un guard Codex :
# PreToolUse codexignore-guard
echo '{"hook_event_name":"PreToolUse","tool_name":"Write","tool_input":{"file_path":"/tmp/test/.env"},"cwd":"/tmp/test"}' \
| python3 ~/.codex/plugins/cache/fusengine-plugins/core-guards/local/scripts/pre-tool-use/codexignore-guard.py
# PostToolUse sync-transcript
echo '{"hook_event_name":"PostToolUse","tool_name":"Bash","tool_input":{"command":"ls"},"cwd":"/tmp/test"}' \
| python3 ~/.codex/plugins/cache/fusengine-plugins/core-guards/local/scripts/post-tool-use/sync-transcript-to-session.pyAdd to your hook script:
echo "[DEBUG] Hook triggered: $TOOL_NAME on $FILE_PATH" >&2