Add `stdin` field to `exec_command` for reliable multiline script execution

Resolved 💬 3 comments Opened Apr 23, 2026 by morozow Closed May 31, 2026

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 /tmp and $TMPDIR are excluded from writable paths
  • write_stdin on 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:

  1. Add an optional stdin field to ExecCommandArgs in unified_exec.rs
  2. After spawning the process, write the stdin content to the pipe and close it
  3. 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-acp agent runtime, codex rust-v0.117.0

Additional information

_No response_

View original on GitHub ↗

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