openai / openai/codex

PreToolUse hook silently skipped (fail-open) when the session working directory no longer exists — tool call runs anyway

Open
#45,293 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

app bug hooks
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 calls tools.exec_command from an exec cell)
  • 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

  1. mkdir -p ~/scratch/hooktest && touch /tmp/bobo and open a new Codex Desktop session on ~/scratch/hooktest.
  2. Ask: run rm -rf /tmp/bobo. Expected and observed: the hook runs (/tmp/codex-hook.log gains a JSON payload), the command is denied. Good.
  3. In a terminal: rm -rf ~/scratch/hooktest (the session stays open in the app). touch /tmp/bobo again.
  4. 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" (or cwd) and the command runs. /tmp/bobo is gone.
  5. 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. $HOME or the tool's effective workdir) 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.