Files
othmanadi__planning-with-files/hooks/codex-hooks.json
T
OthmanAdi eb8af2f02f fix(hooks): send the PostToolUse nudge to the model, once per turn, off read-only tools
closes #239, reported by Raymond Manaloto (@sortakool)

The progress reminder is addressed to Claude:

    [planning-with-files] Update progress.md with what you just did. ...

hooks/claude-hook.sh emitted it as systemMessage, which Claude Code documents
as a warning shown to the USER. So the person saw the instruction after every
Write, Edit and Bash call for a whole session, and the model it is written for
never saw it once. emit_session_start eleven lines above already emitted
hookSpecificOutput.additionalContext for its own event, so the correct shape
was in the same file the whole time. .codex/hooks/post_tool_use.py had the
identical defect, next to a pre_tool_use.py and a run_sh.py that already used
the right shape.

Three defects, one line apart, fixed together:

1. The field. Both routes now emit additionalContext under hookSpecificOutput
   with "hookEventName": "PostToolUse".

2. The throttle. Once per turn rather than once per tool call. The string is a
   constant, so every repeat after the first carries no information: it names
   no tool, no file and no phase. UserPromptSubmit and SessionStart fire once
   per turn and clear a marker; the first post-tool fire of the turn sets it.
   The marker lives in the user's private cache under pwf-turn/, never in the
   plan directory, and is keyed on the plan path plus PWF_SESSION_ID so two
   sessions sharing a plan do not silence each other. A cache root that cannot
   be created skips the throttle rather than the reminder, so a broken cache
   cannot quietly remove the signal instead of the noise.

3. The matcher. Bash comes off PostToolUse on all three manifests: ls, git
   status and grep were tripping a "you changed something, record it" nudge.
   hooks/hooks.json goes to Write|Edit and both Codex manifests to
   apply_patch|Edit|Write. PreToolUse keeps Bash, because a plan reminder
   before a shell command is a different and wanted behaviour. The standalone
   skill route already used Write|Edit, which is further evidence the wider
   matchers were drift rather than intent.

Fixing this surfaced a fourth thing. active_plan_dir() in hooks/claude-hook.sh
resolves through the shared resolver and then fell back to the legacy root
task_plan.md: the #237 fallback removed from the script and Codex routes in
v3.15.0 and missed on the plugin dispatcher. A rejected PLAN_ID or
PWF_PLAN_ROOT now stops there too.

Deliberately not in this change: FileChanged, which would catch a Bash command
that rewrites a file, is a new event surface with its own behaviour and belongs
in a release where it can be tested on its own. The standalone skill route
still emits the sentence as plain stdout from a YAML frontmatter scalar with no
throttle; it already carries the narrow matcher so the volume reported here
never applied to it, and giving it the throttle means either putting cache-key
logic inside a hook scalar or routing it through a new script context across
eleven SKILL.md files.

Tests: 10 in tests/test_post_tool_nudge.py, driving both routes as processes
rather than asserting on source. One proves the two Codex scripts agree on the
marker key by asserting the re-arm deletes the exact file the throttle wrote:
they derive it independently and one resolves a PWF_PLAN_ROOT pin while the
other does not, so a divergence would leave the nudge firing once per session
with nothing in any log.
2026-09-03 01:16:16 +02:00

92 lines
4.1 KiB
JSON

{
"description": "Codex lifecycle hooks for planning-with-files plugin installs",
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume|clear|compact",
"hooks": [
{
"type": "command",
"command": "python3 \"${PLUGIN_ROOT}/.codex/hooks/run_sh.py\" session-start.sh",
"commandWindows": "powershell.exe -NoLogo -NoProfile -NonInteractive -EncodedCommand JgAgACgASgBvAGkAbgAtAFAAYQB0AGgAIAAkAGUAbgB2ADoAUABMAFUARwBJAE4AXwBSAE8ATwBUACAAJwAuAGMAbwBkAGUAeABcAGgAbwBvAGsAcwBcAHAAdwBmAC0AaABvAG8AawAuAGMAbQBkACcAKQAgAHAAbAB1AGcAaQBuAF8AZABpAHMAcABhAHQAYwBoAC4AcAB5AA==",
"statusMessage": "Loading planning context"
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"${PLUGIN_ROOT}/.codex/hooks/run_sh.py\" user-prompt-submit.sh",
"commandWindows": "powershell.exe -NoLogo -NoProfile -NonInteractive -EncodedCommand JgAgACgASgBvAGkAbgAtAFAAYQB0AGgAIAAkAGUAbgB2ADoAUABMAFUARwBJAE4AXwBSAE8ATwBUACAAJwAuAGMAbwBkAGUAeABcAGgAbwBvAGsAcwBcAHAAdwBmAC0AaABvAG8AawAuAGMAbQBkACcAKQAgAHAAbAB1AGcAaQBuAF8AZABpAHMAcABhAHQAYwBoAC4AcAB5AA=="
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash|apply_patch|Edit|Write",
"hooks": [
{
"type": "command",
"command": "python3 \"${PLUGIN_ROOT}/.codex/hooks/pre_tool_use.py\"",
"commandWindows": "powershell.exe -NoLogo -NoProfile -NonInteractive -EncodedCommand JgAgACgASgBvAGkAbgAtAFAAYQB0AGgAIAAkAGUAbgB2ADoAUABMAFUARwBJAE4AXwBSAE8ATwBUACAAJwAuAGMAbwBkAGUAeABcAGgAbwBvAGsAcwBcAHAAdwBmAC0AaABvAG8AawAuAGMAbQBkACcAKQAgAHAAbAB1AGcAaQBuAF8AZABpAHMAcABhAHQAYwBoAC4AcAB5AA==",
"statusMessage": "Checking plan before tool use"
}
]
}
],
"PermissionRequest": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"${PLUGIN_ROOT}/.codex/hooks/permission_request.py\"",
"commandWindows": "powershell.exe -NoLogo -NoProfile -NonInteractive -EncodedCommand JgAgACgASgBvAGkAbgAtAFAAYQB0AGgAIAAkAGUAbgB2ADoAUABMAFUARwBJAE4AXwBSAE8ATwBUACAAJwAuAGMAbwBkAGUAeABcAGgAbwBvAGsAcwBcAHAAdwBmAC0AaABvAG8AawAuAGMAbQBkACcAKQAgAHAAbAB1AGcAaQBuAF8AZABpAHMAcABhAHQAYwBoAC4AcAB5AA=="
}
]
}
],
"PostToolUse": [
{
"matcher": "apply_patch|Edit|Write",
"hooks": [
{
"type": "command",
"command": "python3 \"${PLUGIN_ROOT}/.codex/hooks/post_tool_use.py\"",
"commandWindows": "powershell.exe -NoLogo -NoProfile -NonInteractive -EncodedCommand JgAgACgASgBvAGkAbgAtAFAAYQB0AGgAIAAkAGUAbgB2ADoAUABMAFUARwBJAE4AXwBSAE8ATwBUACAAJwAuAGMAbwBkAGUAeABcAGgAbwBvAGsAcwBcAHAAdwBmAC0AaABvAG8AawAuAGMAbQBkACcAKQAgAHAAbAB1AGcAaQBuAF8AZABpAHMAcABhAHQAYwBoAC4AcAB5AA==",
"statusMessage": "Reviewing changes against plan"
}
]
}
],
"PreCompact": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "python3 \"${PLUGIN_ROOT}/.codex/hooks/run_sh.py\" pre-compact.sh",
"commandWindows": "powershell.exe -NoLogo -NoProfile -NonInteractive -EncodedCommand JgAgACgASgBvAGkAbgAtAFAAYQB0AGgAIAAkAGUAbgB2ADoAUABMAFUARwBJAE4AXwBSAE8ATwBUACAAJwAuAGMAbwBkAGUAeABcAGgAbwBvAGsAcwBcAHAAdwBmAC0AaABvAG8AawAuAGMAbQBkACcAKQAgAHAAbAB1AGcAaQBuAF8AZABpAHMAcABhAHQAYwBoAC4AcAB5AA==",
"statusMessage": "Preparing planning context before compact"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"${PLUGIN_ROOT}/.codex/hooks/stop.py\"",
"commandWindows": "powershell.exe -NoLogo -NoProfile -NonInteractive -EncodedCommand JgAgACgASgBvAGkAbgAtAFAAYQB0AGgAIAAkAGUAbgB2ADoAUABMAFUARwBJAE4AXwBSAE8ATwBUACAAJwAuAGMAbwBkAGUAeABcAGgAbwBvAGsAcwBcAHAAdwBmAC0AaABvAG8AawAuAGMAbQBkACcAKQAgAHAAbAB1AGcAaQBuAF8AZABpAHMAcABhAHQAYwBoAC4AcAB5AA==",
"timeout": 30
}
]
}
]
}
}