Bash PreToolUse hooks cannot observe the effective working directory of exec_command calls
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:
- Treat the session
cwdas the command directory and falsely block safe, repository-scoped commands. - 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
PreToolUseand the correspondingPostToolUse; - equal to the actual fallback directory when the caller omits
workdir; and - retained for the original command when
write_stdinlater 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 asBash; - the common
cwdfield is the working directory for the session; and - Bash
tool_inputusestool_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_