Turn completes prematurely when unified_exec background processes are still running
What version of the Codex App are you using (From “About Codex” dialog)?
Latest from main branch (commit from codex-rs/core)
What subscription do you have?
Pro
What platform is your computer?
Darwin 25.3.0 arm64 arm
What issue are you seeing?
When an agent starts a long-running process via unified_exec (e.g., uv run train.py, npm run build, or any command that takes more than a few seconds), the turn completes immediately after the model's response — without waiting for the process to finish. The agent never receives the process output.
Root cause identified in code:
In codex-rs/core/src/codex.rs, the ResponseEvent::Completed handler only checks has_pending_input():
ResponseEvent::Completed { .. } => {
// ...
needs_follow_up |= sess.has_pending_input().await;
// Turn ends if needs_follow_up is false
}
This does not account for running processes in unified_exec_manager.process_store. When a process is started with exec_command and returns a process_id (indicating it's still running), the turn should not end until:
- The process completes, OR
- The agent explicitly acknowledges detachment
Observed behavior:
- Agent calls
exec_commandwith a long-running command - Process starts, returns
process_id - Model generates response text
ResponseEvent::Completedfireshas_pending_input()returnsfalse- Turn ends — agent loses opportunity to wait for output
What steps can reproduce the bug?
- Start a Codex session
- Ask the agent to run a long-running command:
``sleep 30 && echo "done"
Run and tell me when it finishes``
- Observe that the turn ends immediately after the agent starts the command
- The agent never reports "done" because it doesn't wait for the process
Alternative reproduction with training script:
Run `uv run train.py` and analyze the results when it completes
The agent starts the 5-minute training script but the turn ends within seconds.
Code path:
exec_commandis called → process starts →process_idreturned (process still running)- Model generates text response
ResponseEvent::Completedtriggershas_pending_input()→false(no tool responses queued)needs_follow_up = false- Turn ends
What is the expected behavior?
When a unified_exec process started in the current turn is still running, the turn should NOT complete. Instead:
- Codex should detect running processes via
unified_exec_manager.process_store - Inject a warning message to the model (e.g., "A background terminal is still running...")
- Set
needs_follow_up = trueto continue the turn - Allow the model to either:
- Wait for the process (call
write_stdinto poll) - Explicitly acknowledge detachment
This enables reliable execution of long-running commands and autonomous workflows.
Additional information
I have implemented a fix in branch raman/fix/turn-end-guard-running-processes, commit https://github.com/openai/codex/commit/c2845daf318e70a08ccb579c99a7b45fc8c423dc:
// In ResponseEvent::Completed handler:
if !needs_follow_up && sess.has_running_unified_exec_processes(&turn_context).await {
sess.record_model_warning(
"A background terminal started in this turn is still running. \
If you intend to keep waiting for it, call write_stdin on the existing \
process_id instead of ending the turn. If you are intentionally done \
waiting, explicitly say that you are detaching from the background process.",
&turn_context,
).await;
needs_follow_up = true;
}
Files changed:
codex-rs/core/src/codex.rs— addedhas_running_unified_exec_processes()method, modifiedResponseEvent::Completedhandlingcodex-rs/core/src/unified_exec/mod.rs— madeprocess_store,ProcessEntryaccessiblecodex-rs/core/src/unified_exec/process.rs— madehas_exited()pub(crate)codex-rs/core/src/codex_tests.rs— unit testcodex-rs/core/src/unified_exec/mod_tests.rs— integration tests
All tests pass (3 new tests added).
Context: Discovered while working on autoresearch — autonomous AI research platform where agents run 5-minute training experiments. This bug makes autonomous experiment loops impossible.
I don't currently have PR access. Happy to provide the complete diff or adjust implementation based on maintainer feedback.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗