Bash PreToolUse hooks cannot observe the effective working directory of exec_command calls

Open 💬 0 comments Opened Aug 24, 2026 by mayangzhou-btd

What version of Codex CLI is running?

0.147.0

What subscription do you have?

20X

Which model were you using?

gpt-5.6 Sol

What platform is your computer?

Linux x86_64

What terminal emulator and version are you using (if applicable)?

Tabby

Codex doctor report

no need

What issue are you seeing?

Title: Bash PreToolUse hooks cannot observe the effective working directory of exec_command calls

Summary

A Bash PreToolUse hook cannot determine the effective working directory of a unified exec (exec_command) invocation.

The hook receives:

  • top-level cwd, which represents the Codex session working directory; and
  • tool_input.command, which contains only the shell command.

When an exec_command call specifies a different workdir, that directory is not included in the hook payload. As a result, the hook cannot determine the actual scope of relative paths such as ..

This behavior matches the current Hooks documentation, so this may be a hook schema gap rather than a regression in the existing cwd field.

Environment

  • Codex CLI: 0.147.0
  • Platform: Linux x86_64
  • Hook event: PreToolUse
  • Hook matcher: ^Bash$
  • Tool path: unified exec (exec_command), including a nested call from code mode
  • Workspace type: Android/AOSP Repo workspace containing many Git projects

Reproduction

Start Codex with the session working directory at the root of an AOSP workspace:

/workspace/aosp

Configure a PreToolUse hook for Bash that records the JSON object received on stdin.

Then issue a unified exec call equivalent to:

{
  "cmd": "rg -n \"EffectSpatializer\" .",
  "workdir": "/workspace/aosp/frameworks/media-ex"
}

The intended and actual execution scope of rg . is:

/workspace/aosp/frameworks/media-ex

However, the PreToolUse hook receives a payload equivalent to:

{
  "cwd": "/workspace/aosp",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rg -n \"EffectSpatializer\" ."
  }
}

The payload does not contain:

/workspace/aosp/frameworks/media-ex

The hook process also runs with the session cwd, so the hook cannot recover the invocation directory from its own process working directory.

Actual result

The hook cannot distinguish between these two semantically different calls:

rg -n "EffectSpatializer" .

executed at:

/workspace/aosp

and the same command executed at:

/workspace/aosp/frameworks/media-ex

This distinction matters in an AOSP workspace:

  • Running rg . at the workspace root may scan thousands of repositories and produce excessive output or latency.
  • Running rg . inside one Repo-managed Git project is a normal, bounded search.

A path-aware policy hook must therefore choose between two incorrect behaviors:

  1. Treat the session cwd as the command directory and falsely block safe, repository-scoped commands.
  2. Allow commands with relative paths and lose enforcement for actual workspace-wide scans.

We encountered the first case in practice: a search intended to run under frameworks/media-ex was rejected because the hook could only see the AOSP session root.

Parsing the shell command cannot solve this problem. The effective workdir is supplied as a tool argument and may not appear in the command string as an explicit cd.

Expected result

Please expose the resolved effective working directory of each Bash invocation to PreToolUse and PostToolUse hooks.

An additive field such as the following would preserve the existing meaning of the documented session-level cwd:

{
  "cwd": "/workspace/aosp",
  "tool_cwd": "/workspace/aosp/frameworks/media-ex",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rg -n \"EffectSpatializer\" ."
  }
}

Alternatively, the effective directory could be preserved in the tool-specific input:

{
  "tool_input": {
    "command": "rg -n \"EffectSpatializer\" .",
    "workdir": "/workspace/aosp/frameworks/media-ex"
  }
}

The value should ideally be:

  • the resolved absolute execution directory;
  • present for both direct and code-mode nested unified exec calls;
  • present in both PreToolUse and the corresponding PostToolUse;
  • equal to the actual fallback directory when the caller omits workdir; and
  • retained for the original command when write_stdin later delivers its completion.

A dedicated top-level tool_cwd field may be clearer because it would preserve the documented meaning of the existing session-level cwd.

Impact beyond ripgrep

This affects any hook policy that interprets relative paths, including policies for:

  • rg, find, and other filesystem searches;
  • Git commands;
  • build and test commands;
  • scripts that read or modify files relative to the process directory;
  • monorepos and multi-repository workspaces; and
  • audit logging that needs to record where a command actually ran.

Hooks do not need to be a complete security boundary, but a PreToolUse hook needs the effective execution directory to make reliable decisions about a pending shell command.

Documentation reference

The current Hooks documentation states that:

  • unified exec (exec_command) is exposed to hooks as Bash;
  • the common cwd field is the working directory for the session; and
  • Bash tool_input uses tool_input.command.

Reference:

https://developers.openai.com/codex/hooks

What steps can reproduce the bug?

Uploaded thread: 01a018c0-afd9-7091-9fb7-1b4bf0b533eb

What is the expected behavior?

see abouve

Additional information

_No response_

View original on GitHub ↗