PreToolUse drops exec_command workdir and reports the turn cwd instead of the effective tool cwd

Open 💬 2 comments Opened Jul 11, 2026 by TTC-DEV-0221

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

  1. workdir is a first-class tool argument and defaults to the turn cwd: shell_spec.rs#L26-L36.
  2. The unified exec handler resolves environment_id/workdir for actual execution: exec_command.rs#L131-L150.
  3. Its pre-hook payload independently parses only ExecCommandArgs and emits { "command": args.cmd }, dropping workdir: exec_command.rs#L408-L418.
  4. Classic shell_command has 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).
  5. run_pre_tool_use_hooks always constructs the request with turn_context.cwd: hook_runtime.rs#L163-L183.
  6. 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_cwd through the same path used by actual execution for both exec_command and shell_command.
  • Add optional tool_cwd to the hook request/input schema without changing hook process cwd.
  • Do not swallow invalid workdir/environment_id resolution in the security-hook path.
  • Cover absent, absolute, and relative workdir; selected environments; denying hooks; updatedInput command rewrites; invalid paths; and remote/foreign environment semantics.
  • Assert that the hook-observed tool_cwd and command-observed pwd are identical.

Related but not duplicate

  • #20879 covers native apply_patch lacking 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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗