Windows: hook commands containing a quoted path break under cmd.exe (backslash-escaped quotes)

Resolved 💬 0 comments Opened Jul 11, 2026 by yanchenko Closed Jul 17, 2026

Bug

codex-rs/hooks/src/engine/command_runner.rs spawns a hook command on Windows via cmd.exe /C <command>, passing the entire hook command string as a single argv element to Command::new(comspec).arg("/C").arg(&handler.command).

Rust's Windows argv escaping (std::process, following CommandLineToArgvW rules) rewrites every embedded " in that argument as \". cmd.exe does not understand \". So a perfectly ordinary hook command that quotes its executable path (the documented way to write a path containing spaces):

command = '"C:\Users\me\AppData\Local\Programs\SomeApp\somebinary.exe" notify'

reaches cmd.exe as the literal token \"C:\...\somebinary.exe\" and fails:

'\"C:\Users\me\AppData\Local\Programs\SomeApp\somebinary.exe\"' is not recognized as an internal or external command
EXIT CODE: 1

Codex surfaces this as SessionStart hook (failed) — hook exited with code 1. Any hook whose command quotes its executable path — i.e. any hook on a path containing a space — is unrunnable on Windows.

Repro

  1. Install any hook on Windows via ~/.codex/config.toml whose command value is a quoted absolute path (required whenever the install path contains a space, e.g. anything under %LOCALAPPDATA%\Programs\... with a space in the app name, or C:\Program Files\...).
  2. Trigger the hook event.
  3. Observe the hook fails with exit code 1 and the \"...\" malformed-token error above.

Suggested fix

Use std::os::windows::process::CommandExt::raw_arg() for the cmd.exe branch instead of .arg(), so the command string is passed through to cmd /C verbatim rather than being re-escaped. This is exactly the case raw_arg exists for.

Workaround we shipped downstream

We work around this in our own tool by emitting a quote-free, space-free Windows command string (resolving the path via cmd.exe's for %~sI short-path trick) so the hook command never needs a literal " — see delllusional/DontSpeak#23 for context. That workaround only fixes hooks we control; any other hook with a quoted/spaced path is still affected.

Refs: codex-rs/hooks/src/engine/command_runner.rs (build_command / default_shell_command).

View original on GitHub ↗