Add `stdin` field to `exec_command` for reliable multiline script execution
What variant of Codex are you using?
CLI (via codex-acp agent runtime)
What feature would you like to see?
The problem
When the agent needs to run a multiline script (Python, awk, Node, etc.), the only option today is to encode the entire script as a shell -c string:
/bin/zsh -lc "python3 - <<'PY'\nimport json\ndata = {\"key\": \"value\"}\nprint(json.dumps(data))\nPY"
This is fragile. The script body passes through shell parsing, so any dollar signs, backticks, double quotes, or backslashes in the script interact with the shell's own syntax. The LLM has to get the escaping exactly right for the specific shell, and it frequently doesn't — leading to failed commands, retries, and wasted tokens.
Observed in production (codex-acp):
ERROR codex_core::tools::router:
error=exec_command failed for `/bin/zsh -lc "python3 - <<'PY' ...`
This pattern repeated 4 times in a ~2 hour window as the agent kept retrying with different escaping strategies.
What would help
An optional stdin field on exec_command, so the agent can do:
{
"cmd": "python3 -",
"stdin": "import json\ndata = {\"key\": \"value\"}\nprint(json.dumps(data))\n"
}
The script body goes directly to the process's stdin pipe, never touches shell parsing. No escaping needed, works with any interpreter that reads from stdin (python3 -, bash -s, node -e, awk).
Current workarounds and why they fall short
- heredoc in shell
-c: fragile escaping, shell-dependent behavior - Write to temp file, then execute: blocked by sandbox when
/tmpand$TMPDIRare excluded from writable paths write_stdinon a separate call: requires two tool calls instead of one, and until recently didn't work on non-TTY sessions at all (see https://github.com/openai/codex/issues/18578)
Scope
The plumbing for this mostly exists already. Non-TTY processes are spawned with piped stdin (or will be, once https://github.com/openai/codex/issues/18578 lands). The change would be:
- Add an optional
stdinfield toExecCommandArgsinunified_exec.rs - After spawning the process, write the
stdincontent to the pipe and close it - Collect output as usual
This keeps the API backward-compatible — omitting stdin preserves current behavior.
Platform
- Darwin 25.3.0 arm64 arm
- Observed via
codex-acpagent runtime, codexrust-v0.117.0
Additional information
_No response_
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗