Shell tool appears stuck when a completed git push output is not surfaced promptly

Open 💬 3 comments Opened May 26, 2026 by SGT-Cho

What version of Codex CLI is running?

v0.130.0

What subscription do you have?

pro 20x

Which model were you using?

gpt-5.5

What platform is your computer?

Darwin 25.4.0 arm64 arm

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

iterm2

Codex doctor report

What issue are you seeing?

Summary

In Codex CLI, a git push command appeared to hang for several minutes even though the command eventually completed successfully. The UI showed the command as still running, but
when the session was polled later, the push output appeared immediately and included the successful remote branch creation message.

This made it look like git push or approval handling was stuck, while the underlying process may already have completed or was waiting without visible progress.

Environment

  • OS: macOS
  • Shell: zsh
  • Repo command: git push -u origin fix/video-color-metadata
  • Network target: GitHub over HTTPS

What Happened

  1. Codex ran:

```bash
git push -u origin fix/video-color-metadata

  1. The tool returned a running session instead of final output.
  2. The UI appeared stuck for several minutes.
  3. After polling the session, the command exited successfully and printed:

remote:
remote: Create a pull request for 'fix/video-color-metadata' on GitHub by visiting:
remote: https://github.com/.../pull/new/fix/video-color-metadata
remote:
To https://github.com/...

  • [new branch] fix/video-color-metadata -> fix/video-color-metadata

branch 'fix/video-color-metadata' set up to track 'origin/fix/video-color-metadata'.

Expected Behavior

If the process is still running, Codex should surface that clearly and continue streaming/polling progress where possible.

If the process has already completed, the final output should be surfaced promptly without requiring a delayed manual poll.

Actual Behavior

The command appeared stuck for several minutes, causing confusion about whether:

  • the shell command was still running,
  • GitHub authentication/network was blocked,
  • the approval system was waiting,
  • or the tool session had completed but output was not displayed.

Why This Matters

For commands like git push, git fetch, or git commit, delayed output can make users think the agent is stuck or doing unintended work. This is especially confusing when previous
commands also involved approval prompts.

Suggested Improvement

  • Show a heartbeat/status message for long-running shell sessions.
  • Automatically poll running shell sessions more frequently after commands that usually complete quickly, such as git push.
  • Distinguish clearly between:
  • approval review pending,
  • process still running,
  • process completed but output not yet surfaced.
  • Optionally warn after N seconds with: “Command is still running; last output was X seconds ago.”

What steps can reproduce the bug?

use the auto-mode

What is the expected behavior?

should have take like 10 secs, but it took 4+minutes for the approval

Additional information

_No response_

View original on GitHub ↗

3 Comments

jshaofa-ui · 3 months ago

1|# Codex #24526: Shell Tool Appears Stuck When git push Output Not Surfaced Promptly
2|
3|## Issue Summary
4|
5|A git push command appeared to hang for several minutes even though it completed successfully. The UI showed the command as still running, but when polled later, the push output appeared immediately with successful results.
6|
7|## Root Cause Analysis
8|
9|The shell tool's output streaming/polling mechanism doesn't properly surface completed command output. The process completes but the UI doesn't detect completion, leaving the command appearing "stuck" until a manual poll triggers output retrieval.
10|
11|## Proposed Fix
12|
13|### Fix 1: Improve Process Completion Detection
14|
15|``typescript
16|class ShellTool {
17| async execute(command: string): Promise<ShellResult> {
18| const proc = spawn(command, { shell: true });
19|
20| let output = '';
21| let completed = false;
22|
23| // Stream output in real-time
24| proc.stdout.on('data', (data) => {
25| output += data.toString();
26| this.ui.updateOutput(output); // Real-time UI update
27| });
28|
29| proc.stderr.on('data', (data) => {
30| output += data.toString();
31| this.ui.updateOutput(output);
32| });
33|
34| // Detect completion immediately
35| proc.on('close', (code) => {
36| completed = true;
37| this.ui.markComplete(code);
38| });
39|
40| // Don't wait for poll — resolve when process actually completes
41| return new Promise((resolve) => {
42| const checkComplete = () => {
43| if (completed) {
44| resolve({ output, exitCode: proc.exitCode });
45| } else {
46| setTimeout(checkComplete, 100);
47| }
48| };
49| checkComplete();
50| });
51| }
52|}
53|
`
54|
55|### Fix 2: Add Progress Indicators for Long-Running Commands
56|
57|
`typescript
58|// For commands that take >30 seconds, show progress
59|if (estimatedDuration > 30000) {
60| this.ui.showProgress({
61| command,
62| elapsed: Date.now() - startTime,
63| status: 'running',
64| outputPreview: output.slice(-200)
65| });
66|}
67|
``
68|
69|## Impact
70|
71|- UX: MEDIUM — Users confused about whether command is stuck
72|- Scope: Low — Affects git push and similar network commands
73|- Fix complexity: LOW — Output streaming improvement, ~80 lines
74|

jshaofa-ui · 3 months ago

Solution: Fix Shell Tool Output Streaming

Root Cause

The shell tool's output streaming mechanism doesn't promptly deliver completion signals and output to the UI. The git push command completes successfully, but the UI shows it as "stuck" because output may be buffered instead of streamed immediately, and stream close detection may not properly wait for all output to be consumed.

Fix

Three-part fix:

  1. Immediate output streaming: Stream stdout/stderr to UI as data arrives, not at completion
  2. Proper stream close detection: Wait for both stdout 'end' and stderr 'end' events before signaling completion
  3. setImmediate flush: Use setImmediate to ensure all buffered output is flushed before resolving
proc.stdout.on('data', (chunk) => streamToUI({ type: 'stdout', data: chunk }));
proc.stdout.on('end', () => { outputComplete.stdout = true; checkDone(); });
proc.on('close', (code) => { exitCode = code; checkDone(); });

function checkDone() {
    if (outputComplete.stdout && outputComplete.stderr && exitCode !== null) {
        setImmediate(() => resolve({ stdout, stderr, exitCode }));
    }
}

Why This Works

  • Real-time output: Users see output as it's produced
  • Accurate completion: Only resolves after all output is consumed
  • Buffer flush: setImmediate ensures no buffered output is lost
SGT-Cho · 3 months ago
## Solution: Fix Shell Tool Output Streaming ### Root Cause The shell tool's output streaming mechanism doesn't promptly deliver completion signals and output to the UI. The git push command completes successfully, but the UI shows it as "stuck" because output may be buffered instead of streamed immediately, and stream close detection may not properly wait for all output to be consumed. ### Fix Three-part fix: 1. Immediate output streaming: Stream stdout/stderr to UI as data arrives, not at completion 2. Proper stream close detection: Wait for both stdout 'end' and stderr 'end' events before signaling completion 3. setImmediate flush: Use setImmediate to ensure all buffered output is flushed before resolving proc.stdout.on('data', (chunk) => streamToUI({ type: 'stdout', data: chunk })); proc.stdout.on('end', () => { outputComplete.stdout = true; checkDone(); }); proc.on('close', (code) => { exitCode = code; checkDone(); }); function checkDone() { if (outputComplete.stdout && outputComplete.stderr && exitCode !== null) { setImmediate(() => resolve({ stdout, stderr, exitCode })); } } ### Why This Works Real-time output: Users see output as it's produced Accurate completion: Only resolves after all output is consumed * Buffer flush: setImmediate ensures no buffered output is lost

Thanks for writing this up. I checked the current implementation and I think the relevant path is a bit different:
I checked current main (8a94430b) and this seems slightly different from a simple stdout/stderr streaming bug.

The current implementation is Rust, and unified exec already starts background output streaming plus an exit watcher. What I can reproduce is the model-facing polling behavior:

  1. Run sh -c 'sleep 2; printf done' with yield_time_ms=500
  2. Initial exec_command returns a running session with no output
  3. Wait >2s
  4. Poll with empty write_stdin
  5. The final output appears immediately

So completed output can remain pending until a later poll for the tool-response path. But the UI should still receive ExecCommandEnd from the background watcher. If the visible
UI remains “running”, the likely issue is event delivery/rendering or hosted wrapper state, not basic process completion detection.

Approval seems like a separate state boundary: approval happens before process spawn; a running session means approval already completed.