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.
2 Comments
Confirmed on Windows x86_64 with Codex CLI 0.144.3.
I reproduced this with the turn cwd set to
C:\\work\\barnand anexec_commandcall whoseworkdirwasC:\\work\\wardand whose command wasgit status --short. ThePreToolUsehook received:The hook process cwd was also the turn cwd. This caused a real fail-closed false positive in Ward, a repository-aware policy hook: Ward had correctly adopted paths in the target repository, but Codex gave it neither the effective tool cwd nor the requested
workdir, so it evaluated the operation against the wrong repository. Rebuilding and reinstalling Ward cannot compensate for information absent from the hook contract.I also confirmed the mismatch in current
mainat8acaec73b6:exec_commandresolvesargs.workdirfor execution, whilepre_tool_use_payloadserializes onlyargs.cmd, and the hook request uses the turn cwd. So the report remains reproducible on a newer release and appears platform-independent.Confirmed on codex-cli 0.144.6 running on Fedora 43 x86_64.
I use an unofficial Linux wrapper for the Codex app, so I am not claiming reproduction on an officially supported Linux Desktop release. However, the affected execution path uses the local Codex CLI/app-server, and the observed behavior matches this issue: exec_command honors its per-command workdir, while PreToolUse receives the session cwd and a command-only tool_input.
This prevents repository-aware hooks from reliably determining which repository a command will modify. The proposed resolved tool_cwd field would address my use case.