[Hooks] PreToolUse omits the effective workdir for exec_command and shell_command
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 55/100
Research direction
Start with codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs, codex-rs/core/src/tools/handlers/shell/shell_command.rs, and codex-rs/core/src/hook_runtime.rs to trace workdir resolution and PreToolUse payload construction. Review the existing hook and handler tests, then add regression coverage showing that identical commands with different workdirs produce distinguishable hook inputs and that the chosen cwd contract is consistent.
Written by the indexing model from the issue text.
Description
What version of Codex CLI is running?
codex-cli 0.146.1, which is the latest stable release at the time of filing.
What subscription do you have?
Not relevant to this local CLI hook-dispatch behavior.
Which model were you using?
Not model-dependent. The behavior is in the CLI tool and hook dispatch paths.
What platform is your computer?
Linux 6.18.33.2-microsoft-standard-WSL2 x86_64 x86_64
Codex doctor report
Not included because this is deterministic local hook payload behavior and is also visible in the tagged release and current main source.
What issue are you seeing?
exec_command and shell_command accept a per-call workdir and correctly use it as the command process cwd. However, their PreToolUse hook payload contains only the command. The top-level hook cwd is taken from the turn context, not from the resolved per-call execution cwd.
As a result, a PreToolUse policy hook cannot distinguish these two calls:
- the same relative command executed in the session worktree;
- the same relative command executed in another allowed directory or Git worktree.
The command runs in the requested workdir, but the hook sees the session cwd plus a tool_input containing only the command.
This is not a sandbox escape: the sandbox still enforces its configured writable roots. It is a hook contract gap that prevents correct policy enforcement and audit attribution inside those allowed roots, especially in multi-worktree repositories.
Current main still has the mismatch:
- exec_command resolves environment_args.workdir into the actual cwd:
https://github.com/openai/codex/blob/74b8f8db93e7a53794b8a190b77d1195097d117e/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs#L142-L151 - its PreToolUse payload preserves only args.cmd:
https://github.com/openai/codex/blob/74b8f8db93e7a53794b8a190b77d1195097d117e/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs#L410-L420 - classic shell_command also preserves only command:
https://github.com/openai/codex/blob/74b8f8db93e7a53794b8a190b77d1195097d117e/codex-rs/core/src/tools/handlers/shell/shell_command.rs#L253-L257 - hook_runtime fills request.cwd from turn_context.cwd:
https://github.com/openai/codex/blob/74b8f8db93e7a53794b8a190b77d1195097d117e/codex-rs/core/src/hook_runtime.rs#L165-L185
What steps can reproduce the bug?
- Configure a PreToolUse hook for Bash that records its stdin JSON.
- Start Codex in repository or worktree A.
- Ask Codex to invoke exec_command with:
- cmd set to a relative write such as mkdir hook-cwd-probe
- workdir set to another sandbox-allowed directory or worktree B
- Observe that the directory is created under B.
- Inspect the hook event:
- cwd still identifies the turn/session directory A;
- tool_input is only {"command":"mkdir hook-cwd-probe"};
- the effective workdir B is absent.
- Repeat the same command with workdir A. The hook cannot distinguish the effective targets from the supplied cwd and tool_input.
The same contract loss exists in both unified exec and classic shell handlers.
What is the expected behavior?
Before execution, hooks should receive the effective resolved cwd of the command.
Either of these contracts would make enforcement possible:
- set the top-level PreToolUse cwd to the resolved per-call execution cwd for shell-like tools; or
- preserve workdir in tool_input and document that top-level cwd remains the turn cwd.
The behavior should be consistent for exec_command and shell_command, with regression coverage proving that two identical relative commands using different workdirs produce distinguishable hook inputs.
Additional information
Related but not duplicate:
- #20879 concerns native apply_patch having no per-call workdir at all.
- This issue concerns exec_command and shell_command, which do have a per-call workdir and execute there, but omit it from the hook event.
- #21753 tracks broader hook parity and payload-contract completeness.
A local bridge cannot reconstruct the missing value reliably. Blanket-blocking relative commands or temporary directories would break valid development and test workflows without fixing the contract.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.5k
- Avg merge
- 1m
- Merged PRs (30d)
- 1k
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from openai/codex
-
enhancement remote
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
bug CLI windows-os
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
macOS sandbox blocks hw.optional.arm64 sysctl, causing Flutter to misdetect Apple Silicon as x64 Openbug CLI sandbox
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bug CLI TUI
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
-
CLI config enhancement skills
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
kwakseongjae/auto-hwp#319 ·
-
area:cli bug filter-quality good first issue priority:medium
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
Difficulty 1/5 Under an hour Newbie friendliness 72/100
bevyengine/bevy#25861 ·
-
comp-datalake
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
ClickHouse/ClickHouse#121222 ·
-
A-linter
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
oxc-project/oxc#26863 ·