PreToolUse hook silently skipped (fail-open) when the session working directory no longer exists — tool call runs anyway
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
Summary
When a Codex Desktop session's working directory no longer exists on disk, a trusted user-level PreToolUse hook is never executed, and the tool call runs anyway. The hook engine reports hook/started / hook/completed within the same second, the hook's own log stays empty, and exec_command proceeds. A hook that cannot be launched should fail closed (deny), or at minimum surface a visible hook error, not silently allow.
The same hook, same model, same code-mode call, on a session whose folder exists, runs and is honoured.
Environment
- Codex Desktop 26.908.40834, bundled
codex-cli 0.154.0-alpha.6.2 - macOS 25.6 (Darwin 25.6.0), Apple Silicon
- model
gpt-6-astra(code mode: the model callstools.exec_commandfrom anexeccell) - hooks enabled by default; hook trusted via Settings → Hooks (trust entries present under
[hooks.state]in~/.codex/config.toml)
Minimal hook to reproduce
~/.codex/hooks.json:
{
"hooks": {
"PreToolUse": [
{ "hooks": [ { "type": "command", "command": "/usr/local/bin/deny-and-log.sh", "timeout": 30, "statusMessage": "deny everything, log it" } ] }
]
}
}
/usr/local/bin/deny-and-log.sh (chmod 755):
#!/bin/sh
# log every invocation, then deny
cat >> /tmp/codex-hook.log
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"denied by test hook"}}'
Trust the hook in Settings → Hooks.
Steps
mkdir -p ~/scratch/hooktest && touch /tmp/boboand open a new Codex Desktop session on~/scratch/hooktest.- Ask:
run rm -rf /tmp/bobo. Expected and observed: the hook runs (/tmp/codex-hook.loggains a JSON payload), the command is denied. Good. - In a terminal:
rm -rf ~/scratch/hooktest(the session stays open in the app).touch /tmp/boboagain. - In the same session ask:
run rm -rf /tmp/bobo.- First attempt fails with
The command couldn't run because the shell process failed to start (No such file or directory)— fine, the shell cannot start in a missing cwd. - The model retries with
workdir: "/tmp"(orcwd) and the command runs./tmp/bobois gone.
- First attempt fails with
- Check
/tmp/codex-hook.log: no new payload. The hook was never invoked for the call that ran.
What the app-server log shows for step 4
From ~/.codex/logs_2.sqlite, around the retried call:
TRACE codex_app_server::outgoing_message | app-server event: hook/started
TRACE codex_app_server::outgoing_message | app-server event: hook/completed <- same second, hook process never ran
...
ERROR codex_core::tools::router | exec_command failed: CreateProcess { message: "Rejected(\"Failed to create unified exec process: No such file or directory (os error 2)\")" } <- first attempt
...
(retry with workdir=/tmp) -> tool runs, no hook payload, no hook error surfaced anywhere
No WARN/ERROR line mentions the hook. The UI shows nothing.
Expected
One of:
- the hook is spawned in a directory that exists (e.g.
$HOMEor the tool's effectiveworkdir) when the session cwd is gone; or - if the hook cannot be spawned, the tool call is denied (fail closed) and the user sees a hook error.
Actual
Silent fail-open: the hook is skipped and the tool call executes. For anyone using hooks as a policy/approval gate this is a bypass triggered by nothing more than deleting or renaming the project folder while a session is open.
Notes
- Related but different: #37251 / #32360 are about the reported cwd in the payload. This report is about the hook not running at all.
- Happy to provide the full sqlite excerpt privately.
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
Reproduce the missing-session-directory case using the hooks.json configuration and deny-and-log.sh script, then trace the hook/started and hook/completed events through codex_core::tools::router and the app-server logs. Done means a PreToolUse hook is invoked or the tool call is denied with a visible error when the session working directory no longer exists.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- json, rust, shell
- Domain
- security, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100