Interrupted synchronous `PostToolUse` emits `hook/started` without `hook/completed`
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.5k
- PR merge metrics
- PR metrics pending
Description
After the interrupted turn has ended, app-server consumers still see the hook run as started, with
no terminal state distinguishing cancellation from continued execution.
Versions
- Platform: Windows 11
- Originally observed:
codex-cli 0.155.0-alpha.9.2 - Reproduced with an isolated fixture:
codex-cli 0.155.1
The current official documentation says synchronous command hooks are awaited before Codex
continues:
Codex hooks documentation.
Reproduction
The Windows reproduction used an empty working directory containing target.txt:
before
.codex/hooks.json configured one synchronous PostToolUse hook:
{
"hooks": {
"PostToolUse": [
{
"matcher": "^apply_patch$",
"hooks": [
{
"type": "command",
"commandWindows": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File .codex\\blocking-post-tool-use.ps1",
"command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File .codex\\blocking-post-tool-use.ps1",
"timeout": 180
}
]
}
]
}
}
.codex/blocking-post-tool-use.ps1 recorded that the hook had started, then blocked:
$ErrorActionPreference = 'Stop'
$payload = [Console]::In.ReadToEnd() | ConvertFrom-Json
$markerPath = Join-Path $PSScriptRoot 'hook-started.json'
$payload | ConvertTo-Json -Depth 20 -Compress |
Set-Content -LiteralPath $markerPath -Encoding utf8
Start-Sleep -Seconds 180
The Codex app server was launched from that directory with plugins disabled:
codex --dangerously-bypass-hook-trust `
-c features.plugins=false `
-c features.hooks=true `
app-server --stdio
An ephemeral turn applied this patch:
Call apply_patch exactly once with this exact patch and then stop:
*** Begin Patch
*** Update File: target.txt
@@
-before
+after
*** End Patch
After both hook/started(postToolUse, run.id) and .codex/hook-started.json were observed,
turn/interrupt was sent. The relevant event sequence was:
apply_patch
hook/started(postToolUse, run.id)
turn/interrupt
turn/completed(status=interrupted)
In the verified run, the turn reached status interrupted, but no matching hook/completed was
observed for the same run.id during the following five seconds. Plugins were disabled, and this
was the only configured hook.
Requested observable result
A cancelled synchronous hook run is represented by a terminal outcome with the same run.id,
without delivering its output to the model. The existing core/tests/suite/hooks.rs coverage
excludes Windows, where this reproduction was verified.
Source locations
core/src/hook_runtime.rs::run_post_tool_use_hooks publishes HookStarted before awaiting the hook
and publishes HookCompleted only after the await returns. The tool remains non-terminal in
core/src/tools/registry.rs::dispatch_any_with_terminal_outcome during that wait, while
core/src/tools/parallel.rs aborts the non-terminal dispatch task on interruption. Once that task
is dropped, no remaining path publishes HookCompleted for the started run.
Related issues
#46060 covers Windows process-tree cleanup and
missing lifecycle events for non-builtin asynchronous hooks. This report covers a synchronous hook
that publishes hook/started but loses its terminal event after turn interruption.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in core/src/hook_runtime.rs::run_post_tool_use_hooks and trace interruption through core/src/tools/registry.rs::dispatch_any_with_terminal_outcome and core/src/tools/parallel.rs. Review core/tests/suite/hooks.rs, noting its current Windows exclusion, and reproduce the isolated synchronous PostToolUse case. Done means interruption yields a terminal hook outcome with the same run.id, without sending hook output to the model, with Windows coverage.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 50/100