Codex¶
ff hook codex installs a snapshot plugin and the shipped skill for Codex. Run /hooks in Codex and approve the hook by hash; an unreviewed hook is skipped. Repeat the review after hook changes.
Install¶
ff hook codex
Activate¶
When codex is on PATH, the installer attempts plugin registration automatically. If registration fails or Codex is unavailable, run the command it prints. The selector is fufu@<marketplace-name>: usually fufu@fufu, but an existing marketplace keeps its own name.
Start or restart Codex, run /hooks, and review and approve fufu's hook. The skill requires no command approval.
Verify¶
ff hook -l
ff doctor --no-fetch
ff doctor cannot read Codex's trust state, so it always retains the review reminder. An installed-file ok does not prove approval or capture. Follow the capture-and-recovery check in the running client.
Files changed¶
The installer writes the owned plugin directory ~/.agents/plugins/fufu/ — the legacy .codex-plugin/plugin.json manifest, hooks/hooks.json, and the skill under skills/fufu/ — and merges one entry named fufu into the personal marketplace ~/.agents/plugins/marketplace.json. The marketplace is not fufu's file: an existing name, other entries, and key order survive under the shared ownership rules, and a file that is not valid JSON is refused untouched. Personal files should stay outside the plugin directory.
$ ff hook codex
codex plugin written to ~/.agents/plugins/fufu
marketplace entry written to ~/.agents/plugins/marketplace.json
skill written to ~/.agents/plugins/fufu/skills/fufu
Codex is not on PATH — run: codex plugin add fufu@fufu
Codex trusts a hook by its hash: run /hooks in Codex to review this one, or it is skipped and nothing captures
$ find ~/.agents -type f | sort
~/.agents/plugins/fufu/.codex-plugin/plugin.json
~/.agents/plugins/fufu/hooks/hooks.json
~/.agents/plugins/fufu/skills/fufu/SKILL.md
~/.agents/plugins/marketplace.json
$ cat ~/.agents/plugins/fufu/hooks/hooks.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|apply_patch",
"hooks": [
{
"type": "command",
"command": "\"/usr/local/bin/ff\" trigger codex"
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "\"/usr/local/bin/ff\" trigger codex"
}
]
}
],
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "\"/usr/local/bin/ff\" trigger codex"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"/usr/local/bin/ff\" trigger codex"
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "\"/usr/local/bin/ff\" trigger codex"
}
]
}
]
}
}
$ cat ~/.agents/plugins/marketplace.json
{
"name": "fufu",
"plugins": [
{
"name": "fufu",
"source": {
"source": "local",
"path": "./.agents/plugins/fufu"
},
"policy": {
"installation": "INSTALLED_BY_DEFAULT",
"authentication": "ON_INSTALL"
},
"category": "Developer Tools"
}
]
}
Hook commands use the absolute path of the installing binary, double-quoted; the example uses /usr/local/bin/ff. When the binary path or hook definitions change, the installer changes the plugin version and registers it again so Codex refreshes its cache.
The manifest uses .codex-plugin/plugin.json for compatibility with Codex 0.153 and 0.154. Those versions do not load this plugin's hooks from the newer root plugin.json format.
PreToolUse attempts capture before Bash or apply_patch calls. UserPromptSubmit captures and can deliver the briefing. SessionStart rebriefs after a startup, resume, clear, or compaction; Stop captures the final edit of a turn; SessionEnd captures once more at the end. Codex captures and tallies recognized Git writes but returns no pre-tool coaching or denial reply, including under strict policy.
Remove¶
ff unhook codex removes the plugin directory and fufu's marketplace entry. Codex forgets the plugin on its next start; the printed codex plugin remove fufu@<marketplace-name> command clears its cache.
$ ff unhook codex
codex removed ~/.agents/plugins/fufu
removed the fufu entry from ~/.agents/plugins/marketplace.json
Codex forgets it on the next start; codex plugin remove fufu@fufu clears its cache
$ find ~/.agents -type f | sort
~/.agents/plugins/marketplace.json
$ cat ~/.agents/plugins/marketplace.json
{
"name": "fufu",
"plugins": []
}
An entry beside fufu's in the marketplace keeps its place, and the file keeps its name.
Troubleshooting and migration¶
If configuration is installed but no capture appears, check that codex plugin list shows the installer's selector as installed and that /hooks has approved it. ff hook -u repairs a plugin missing an event fufu has since added; ff doctor --fix repairs partial or stale managed configuration and skills; neither can approve a hook.
Before this plugin, fufu merged hook entries into ~/.codex/hooks.json and wrote the skill to ~/.codex/skills/fufu/. ff hook codex writes the plugin, verifies it reads back, and then strips those: its own entries from ~/.codex/hooks.json (foreign entries stay), the old skill directory (other skills under ~/.codex/skills/ stay), and the marked [mcp_servers.fufu] block a fufu before v0.15 wrote into ~/.codex/config.toml (an unmarked table is left alone). Each strip is best-effort and reported; a file that will not parse is named and left. The old entries no longer read as wired in ff hook -l, since the plugin is the mechanism now.
If the marketplace file already carries a name — tower, say — the plugin registers under it, and the installer prints codex plugin add fufu@<name> accordingly.