`write_stdin` fails on non-TTY sessions – no way to pipe input without a full PTY

Open 💬 1 comment Opened Apr 19, 2026 by morozow

What version of Codex CLI is running?

rust-v0.117.0 (via codex-acp v0.11.1)

What subscription do you have?

Pro

Which model were you using?

_No response_

What platform is your computer?

Darwin 25.3.0 arm64 arm

What terminal emulator and version are you using (if applicable)?

_No response_

What issue are you seeing?

What happened

When the agent calls exec_command with tty=false (the default) and later tries to send input to the running process via write_stdin, it immediately fails with:

stdin is closed for this session; rerun exec_command with tty=true to keep stdin open

This came up multiple times over a ~12 hour agent session:

[agent:codex-acp] 2026-04-18T21:31:50.928396Z ERROR codex_core::tools::router:
  error=write_stdin failed: stdin is closed for this session;
  rerun exec_command with tty=true to keep stdin open

[agent:codex-acp] 2026-04-18T23:54:27.017650Z ERROR codex_core::tools::router:
  error=write_stdin failed: stdin is closed for this session;
  rerun exec_command with tty=true to keep stdin open

[agent:codex-acp] 2026-04-19T10:13:45.023769Z ERROR codex_core::tools::router:
  error=write_stdin failed: stdin is closed for this session;
  rerun exec_command with tty=true to keep stdin open

What I expected

write_stdin should work on non-TTY sessions, at least when the process was spawned with a pipe. A full PTY shouldn't be required just to send input to stdin.

Why this happens

In process_manager.rs, write_stdin has a hard gate:

if !request.input.is_empty() {
    if !tty {
        return Err(UnifiedExecError::StdinClosed);
    }
}

Non-TTY processes are spawned via spawn_process_no_stdin_with_inherited_fds, which uses PipeStdinMode::Null — stdin is literally /dev/null. So there's no pipe to write to.

The thing is, the infrastructure for a piped stdin already exists in pipe.rs: there's a PipeStdinMode::Piped variant and a spawn_process function that uses it. But process_manager.rs never takes that path — the branching is binary: full PTY or null stdin, nothing in between.

Impact

The agent can't pipe content to interpreters (python3 -, bash -s, node -e, etc.) without spinning up a full PTY. This matters because piping via stdin is the most reliable way to run multiline scripts — it avoids shell escaping issues with -c and doesn't need temp file writes that may hit sandbox restrictions.

Each failure wastes a tool call and tokens on a retry that the agent has to figure out on its own.

Platform

  • Darwin 25.3.0 arm64 arm
  • Bug observed via codex-acp agent runtime
  • Namespace in logs: codex_core::tools::router

What steps can reproduce the bug?

  1. Call exec_command with tty=false (the default) — e.g. start python3 - or cat
  2. On the same session, call write_stdin with any non-empty input
  3. Observe: immediately returns stdin is closed for this session; rerun exec_command with tty=true to keep stdin open

What is the expected behavior?

write_stdin should succeed on non-TTY sessions when the process was spawned with a piped stdin. A full PTY shouldn't be required just to send input to a process.

Additional information

_No response_

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗