PreToolUse drops exec_command workdir and reports the turn cwd instead of the effective tool cwd
What version of Codex CLI is running?
codex-cli 0.144.1. The bug is also present on main at 5c19155cbd93bfa099016e7487259f61669823ff.
What platform is your computer?
macOS 15.7.3, arm64. The payload loss is platform-independent.
What issue are you seeing?
exec_command honors its workdir when executing a command, but its PreToolUse hook receives only the turn/session cwd and { "command": ... }. The requested and resolved command cwd are both absent from hook stdin.
Example tool call while the Codex turn cwd is /Users/me:
{
"cmd": "git push -u origin main",
"workdir": "/Users/me/knowledge"
}
Relevant fields observed by a PreToolUse hook:
{
"cwd": "/Users/me",
"tool_name": "Bash",
"tool_input": {
"command": "git push -u origin main"
}
}
A repository-aware policy hook therefore evaluates /Users/me rather than /Users/me/knowledge. A fail-closed hook blocks a legitimate operation as a false positive; a hook that trusts the reported cwd can apply policy to the wrong repository and fail open.
Embedding the repository in the command (git -C /Users/me/knowledge push ...) makes the call classifiable, but that is only a workaround.
Source trace
workdiris a first-class tool argument and defaults to the turn cwd: shell_spec.rs#L26-L36.- The unified exec handler resolves
environment_id/workdirfor actual execution: exec_command.rs#L131-L150. - Its pre-hook payload independently parses only
ExecCommandArgsand emits{ "command": args.cmd }, droppingworkdir: exec_command.rs#L408-L418. - Classic
shell_commandhas the same split: it resolves workdir for execution (shell_command.rs#L184-L213) but emits a command-only pre-hook payload (shell_command.rs#L249-L253). run_pre_tool_use_hooksalways constructs the request withturn_context.cwd: hook_runtime.rs#L163-L183.- That cwd is serialized into hook stdin and used as the hook process cwd: pre_tool_use.rs#L105-L110, pre_tool_use.rs#L164-L185.
Expected behavior
A PreToolUse policy hook must be able to determine the same effective cwd that the pending shell-like tool will use, including relative workdir resolution against the selected environment cwd.
Please preserve the stable Bash tool_input == { "command": ... } shape and expose the resolved tool cwd separately, for example as an optional top-level tool_cwd. Keeping existing top-level cwd as the hook process/turn cwd is safer than repurposing it: today it also controls where relative hook commands are spawned, and a foreign/remote environment path may not be a valid local hook-process cwd.
Suggested contract:
cwd: unchanged local cwd used to launch the hook command.tool_cwd: normalized effective cwd of the pending tool execution.tool_input: unchanged command-only Bash contract.- Policy hooks use
tool_cwd ?? cwd.
Suggested implementation and acceptance tests
- Resolve
tool_cwdthrough the same path used by actual execution for bothexec_commandandshell_command. - Add optional
tool_cwdto the hook request/input schema without changing hook process cwd. - Do not swallow invalid
workdir/environment_idresolution in the security-hook path. - Cover absent, absolute, and relative workdir; selected environments; denying hooks;
updatedInputcommand rewrites; invalid paths; and remote/foreign environment semantics. - Assert that the hook-observed
tool_cwdand command-observedpwdare identical.
Related but not duplicate
- #20879 covers native
apply_patchlacking per-call workdir context. - #26675 covers relative plugin hook command resolution.
This report is specifically about shell-like tools already having workdir for execution but losing it at the PreToolUse boundary.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗