CLI freezes with "No messages returned" error - never recovers

Status Closed — not planned
Reported on v2.1.12
Maintainer reply None cached
Activity 11 comments · opened Jan 18, 2026 · closed Mar 2, 2026

When running Claude Code CLI in a long-running automated script, I occasionally encounter a "No messages returned" error that causes the CLI to freeze indefinitely. The process never completes or exits - it just hangs.

Context

I'm building an autonomous coding agent that uses Claude Code to implement features from a PRD (Product Requirements Document). I have a bash script (affectionately named "Ralph Wiggum") that iterates over user stories, having Claude implement them one at a time.

Command Being Run

cat "$SCRIPT_DIR/prompt.md" | claude --dangerously-skip-permissions --verbose --print 2>&1 | tee "$TEMP_OUTPUT"

The prompt file contains instructions for Claude to:

  1. Read a JSON-based PRD with user stories
  2. Pick the highest priority incomplete story
  3. Implement it following existing codebase patterns
  4. Run tests and linting
  5. Commit the changes

Error Output

we didn't catch the error

This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason:
Error: No messages returned
    at GO9 (file:///opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js:5390:73)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)

Observed Behavior

  1. Claude begins processing the prompt normally
  2. At some point during execution, the error above is printed to stderr
  3. The process does not exit - it hangs indefinitely
  4. No further output is produced
  5. Ctrl+C is required to terminate the process

Expected Behavior

  • The CLI should either recover from this transient error and continue, or
  • Exit with a non-zero status code so the calling script can retry

Workaround Attempted

I've implemented retry logic in my bash script that:

  1. Captures output to a temp file
  2. Greps for "No messages returned"
  3. Retries up to 3 times with 5-second delays

However, this doesn't help because the CLI freezes - it doesn't exit, so the script can't detect completion and retry. The only option is to manually Ctrl+C.

Reproduction

This is intermittent and hard to reproduce on demand. It seems to occur:

  • During long-running sessions with many tool calls
  • More frequently when iterating over multiple tasks
  • Possibly related to rate limiting or API timeouts?

Environment

| Component | Version |
|-----------|---------|
| Claude Code | 2.1.12 |
| macOS | 26.2 (Build 25C56) |
| Darwin Kernel | 25.2.0 (ARM64) |
| Node.js | v23.11.0 |
| npm | 10.9.2 |
| Architecture | Apple Silicon (arm64, M1) |

Minimal Reproduction Script

#!/bin/bash
# "Ralph Wiggum" - iterates over specs to implement features

MAX_ITERATIONS=10
TEMP_OUTPUT=$(mktemp)
trap "rm -f $TEMP_OUTPUT" EXIT

for i in $(seq 1 $MAX_ITERATIONS); do
  echo "Iteration $i"
  
  # This command occasionally triggers the freeze
  cat prompt.md | claude --dangerously-skip-permissions --verbose --print 2>&1 | tee "$TEMP_OUTPUT" || true
  
  # Check for completion (never reached when frozen)
  if grep -q "COMPLETE" "$TEMP_OUTPUT"; then
    echo "Done!"
    exit 0
  fi
done

Suggested Fix

The unhandled promise rejection at cli.js:5390 should be caught and either:

  1. Retried with exponential backoff internally
  2. Result in a clean exit with a specific error code (e.g., exit 2 for "retry recommended")
  3. At minimum, not freeze the entire process

Additional Context

The error message "we didn't catch the error" suggests this is a known gap in error handling. The stack trace points to GO9 function in cli.js:5390 which is likely minified code, but the issue is clearly an unhandled async rejection.

View original on GitHub ↗

11 Comments

github-actions[bot] · 7 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/15918
  2. https://github.com/anthropics/claude-code/issues/18880
  3. https://github.com/anthropics/claude-code/issues/16861

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

brunosantoscodexforgeceo · 7 months ago

I'm having the same issues but I'm not using --resume flag. The 3 possible duplicates suggested are not the same case.

zanganeh · 7 months ago

Exactly same problem:

This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason:
Error: No messages returned
at gAf (B:/~BUN/root/claude.exe:5329:78)
at processTicksAndRejections (native:7:39)

I'm using:
while ($true) { $env:IS_SANDBOX="1"; $prompt = Get-Content ralph/prompt.md -Raw; claude $prompt --dangerously-skip-permissions --chrome --print --no-session-persistence; Start-Sleep -Seconds 2 }

And only happen on specific md file which is very hard to find out what is that!

minovap · 7 months ago

Workaround: Two-part solution for reliable completion detection

We ran into this exact issue building an autonomous "Ralph Wiggum" agent loop . After much debugging, we found a reliable workaround with two parts. It's more complex than a script like this should need to be, but it's robust and we've been running it in production without any issues. We plan to simplify once this bug is fixed in Claude Code.

Part 1: Use --output-format stream-json to detect completion

The root cause is that Claude completes its work successfully, but hangs during exit/cleanup. The key insight: the "type":"result" JSON message is emitted BEFORE the hang occurs.

# Instead of:
cat prompt.md | claude --dangerously-skip-permissions --print 2>&1 | tee "$TEMP_OUTPUT"

# Use:
```
PROMPT="$(<prompt.md)"
claude --dangerously-skip-permissions -p "$PROMPT" --output-format stream-json --verbose 2>&1 &
CLAUDE_PID=$!

                                                                                                                                                      
  # Watch the stream for the result message                                                                                                           

while IFS= read -r line; do
echo "$line" >> "$TEMP_OUTPUT"

if [[ "$line" == '"type":"result"' ]]; then
RESULT_RECEIVED=true
# Session complete - give 2s to exit, then kill if hung
( sleep 2; kill $CLAUDE_PID 2>/dev/null ) &
KILLER_PID=$!
break
fi
done < <(cat /proc/$CLAUDE_PID/fd/1 2>/dev/null)

wait $CLAUDE_PID 2>/dev/null
kill $KILLER_PID 2>/dev/null

# Extract the actual result text using jq
RESULT_TEXT=$(grep '"type":"result"' "$TEMP_OUTPUT" | jq -r '.result // empty' 2>/dev/null | head -1)
echo "$RESULT_TEXT"

                                                                                                                                                      
  Part 2: Add explicit completion signals to your prompt                                                                                              
                                                                                                                                                      
  In your prompt.md, tell Claude to output a specific signal when done. This gives you a reliable way to detect task completion vs just "Claude stopped talking":                                                                                                                                   
                                                                                                                                                      
  ## Stop Condition                                                                                                                                   
                                                                                                                                                      
  After completing a task, reply with one of these signals:                                                                                           
                                                                                                                                                      
  If ALL tasks are complete:                                                                                                                          
`<promise>COMPLETE</promise>`                                                                                                                    
                                                                                                                                                      
  If you finished this iteration but more tasks remain:                                                                                               
  `<promise>END_OF_STORY</promise>`                                                                                                                     
                                                                                                                                                      
  This signals to the runner that you finished your work for this iteration.                                                                          
                                                                                                                                                      
  Then in your bash script, check for these signals:                                                                                                  
                                                                                                                                                      
  # Check for all-done signal                                                                                                                         

if grep -q "<promise>COMPLETE</promise>" "$TEMP_OUTPUT" 2>/dev/null; then
echo "All tasks completed!"
exit 0
fi

                                                                                                                                                      
  # Check for single iteration completion                                                                                                             

if grep -q "<promise>END_OF_STORY</promise>" "$TEMP_OUTPUT" 2>/dev/null; then
echo "✓ Iteration completed successfully"
fi

                                                                                                                                                      
  Why this works                                                                                                                                      
                                                                                                                                                      
  1. stream-json outputs JSON objects line-by-line, including {"type":"result", "result":"...", "subtype":"success"}                                  
  2. This result message is sent before the unhandled promise rejection occurs                                                                        
  3. Once we see the result, we know Claude finished - if it hangs on exit, we just kill it after 2 seconds                                           
  4. The <promise> tags give us semantic completion detection (did Claude actually finish the task, or just stop?)                                    
                                                                                                                                                      
 # Full working example                                                                                                                                
                                                                                                                                                      
  Here's the minimal reproduction script rewritten with our fix:                                                                                      
                                                                           
                                                                                                                                                      
  ```
                                                                                                                                                      
  #!/bin/bash                                                                                                                                         
  # "Ralph Wiggum" - iterates over specs to implement features                                                                                        
  # FIX: Uses stream-json + explicit completion signals                    
MAX_ITERATIONS=10                                                                                                                                   
  TEMP_OUTPUT=$(mktemp)                                                                                                                               
  trap "rm -f $TEMP_OUTPUT" EXIT                                                                                                                      
                                                                                                                                                      
  for i in $(seq 1 $MAX_ITERATIONS); do                                                                                                               
    echo "═══════════════════════════════════════════"                                                                                                
    echo "  Iteration $i of $MAX_ITERATIONS"                                                                                                          
    echo "═══════════════════════════════════════════"                                                                                                
                                                                                                                                                      
    PROMPT="$(<prompt.md)"                                                                                                                            
    > "$TEMP_OUTPUT"                                                                                                                                  
                                                                                                                                                      
    # Run with stream-json to detect completion reliably                                                                                              
    claude --dangerously-skip-permissions -p "$PROMPT" --output-format stream-json --verbose 2>&1 &                                                   
    CLAUDE_PID=$!                                                                                                                                     
                                                                                                                                                      
    RESULT_RECEIVED=false                                                                                                                             
    while IFS= read -r line; do                                                                                                                       
      echo "$line" >> "$TEMP_OUTPUT"                                                                                                                  
                                                                                                                                                      
      if [[ "$line" == *'"type":"result"'* ]]; then                                                                                                   
        RESULT_RECEIVED=true                                                                                                                          
        ( sleep 2; kill $CLAUDE_PID 2>/dev/null ) &                                                                                                   
        KILLER_PID=$!                                                                                                                                 
        break                                                                                                                                         
      fi                                                                                                                                              
    done < <(cat /proc/$CLAUDE_PID/fd/1 2>/dev/null || wait $CLAUDE_PID)                                                                              
                                                                                                                                                      
    wait $CLAUDE_PID 2>/dev/null                                                                                                                      
    kill $KILLER_PID 2>/dev/null                                                                                                                      
                                                                                                                                                      
    # Show result                                                                                                                                     
    RESULT_TEXT=$(grep '"type":"result"' "$TEMP_OUTPUT" | jq -r '.result // empty' 2>/dev/null | head -1)                                             
    [ -n "$RESULT_TEXT" ] && echo "$RESULT_TEXT"                                                                                                      
                                                                                                                                                      
    if [ "$RESULT_RECEIVED" = true ]; then                                                                                                            
      echo "✓ Session completed (detected via stream-json)"                                                                                           
    else                                                                                                                                              
      echo "⚠️  No result received, continuing anyway..."                                                                                             
    fi                                                                                                                                                
                                                                                                                                                      
    # Check for completion signals from prompt                                                                                                        
    if grep -q "<promise>COMPLETE</promise>" "$TEMP_OUTPUT" 2>/dev/null; then                                                                         
      echo "Done - all tasks complete!"                                                                                                               
      exit 0                                                                                                                                          
    fi                                                                                                                                                
                                                                                                                                                      
    if grep -q "<promise>END_OF_STORY</promise>" "$TEMP_OUTPUT" 2>/dev/null; then                                                                     
      echo "✓ Story completed, continuing to next..."                                                                                                 
    fi                                                                                                                                                
                                                                                                                                                      
    sleep 2                                                                                                                                           
  done                                                                                                                                                
                                                                                                                                                      
  echo "Reached max iterations ($MAX_ITERATIONS)"                                                                                                     
  exit 1    

We've been running this in production for days zero freezes. Before this we had problems every other story claude code was finishing. The combination of stream-json detection + graceful kill + explicit completion signals makes it bulletproof.

Hope this helps others hitting the same issue!

zanganeh · 7 months ago
Workaround: Two-part solution for reliable completion detection We ran into this exact issue building an autonomous "Ralph Wiggum" agent loop . After much debugging, we found a reliable workaround with two parts. It's more complex than a script like this should need to be, but it's robust and we've been running it in production without any issues. We plan to simplify once this bug is fixed in Claude Code. Part 1: Use --output-format stream-json to detect completion The root cause is that Claude completes its work successfully, but hangs during exit/cleanup. The key insight: the "type":"result" JSON message is emitted BEFORE the hang occurs. # Instead of: cat prompt.md | claude --dangerously-skip-permissions --print 2>&1 | tee "$TEMP_OUTPUT" # Use: `` PROMPT="$(<prompt.md)" claude --dangerously-skip-permissions -p "$PROMPT" --output-format stream-json --verbose 2>&1 & CLAUDE_PID=$! ` # Watch the stream for the result message ` while IFS= read -r line; do echo "$line" >> "$TEMP_OUTPUT" if [[ "$line" == *'"type":"result"'* ]]; then RESULT_RECEIVED=true # Session complete - give 2s to exit, then kill if hung ( sleep 2; kill $CLAUDE_PID 2>/dev/null ) & KILLER_PID=$! break fi done < <(cat /proc/$CLAUDE_PID/fd/1 2>/dev/null) wait $CLAUDE_PID 2>/dev/null kill $KILLER_PID 2>/dev/null # Extract the actual result text using jq RESULT_TEXT=$(grep '"type":"result"' "$TEMP_OUTPUT" | jq -r '.result // empty' 2>/dev/null | head -1) echo "$RESULT_TEXT" ` Part 2: Add explicit completion signals to your prompt In your prompt.md, tell Claude to output a specific signal when done. This gives you a reliable way to detect task completion vs just "Claude stopped talking": ## Stop Condition After completing a task, reply with one of these signals: If ALL tasks are complete: <promise>COMPLETE</promise> If you finished this iteration but more tasks remain: <promise>END_OF_STORY</promise> This signals to the runner that you finished your work for this iteration. Then in your bash script, check for these signals: # Check for all-done signal ` if grep -q "<promise>COMPLETE</promise>" "$TEMP_OUTPUT" 2>/dev/null; then echo "All tasks completed!" exit 0 fi ` # Check for single iteration completion ` if grep -q "<promise>END_OF_STORY</promise>" "$TEMP_OUTPUT" 2>/dev/null; then echo "✓ Iteration completed successfully" fi ` Why this works 1. stream-json outputs JSON objects line-by-line, including {"type":"result", "result":"...", "subtype":"success"} 2. This result message is sent before the unhandled promise rejection occurs 3. Once we see the result, we know Claude finished - if it hangs on exit, we just kill it after 2 seconds 4. The tags give us semantic completion detection (did Claude actually finish the task, or just stop?) # Full working example Here's the minimal reproduction script rewritten with our fix: ` #!/bin/bash # "Ralph Wiggum" - iterates over specs to implement features # FIX: Uses stream-json + explicit completion signals MAX_ITERATIONS=10 TEMP_OUTPUT=$(mktemp) trap "rm -f $TEMP_OUTPUT" EXIT for i in $(seq 1 $MAX_ITERATIONS); do echo "═══════════════════════════════════════════" echo " Iteration $i of $MAX_ITERATIONS" echo "═══════════════════════════════════════════" PROMPT="$(<prompt.md)" > "$TEMP_OUTPUT" # Run with stream-json to detect completion reliably claude --dangerously-skip-permissions -p "$PROMPT" --output-format stream-json --verbose 2>&1 & CLAUDE_PID=$! RESULT_RECEIVED=false while IFS= read -r line; do echo "$line" >> "$TEMP_OUTPUT" if [[ "$line" == *'"type":"result"'* ]]; then RESULT_RECEIVED=true ( sleep 2; kill $CLAUDE_PID 2>/dev/null ) & KILLER_PID=$! break fi done < <(cat /proc/$CLAUDE_PID/fd/1 2>/dev/null || wait $CLAUDE_PID) wait $CLAUDE_PID 2>/dev/null kill $KILLER_PID 2>/dev/null # Show result RESULT_TEXT=$(grep '"type":"result"' "$TEMP_OUTPUT" | jq -r '.result // empty' 2>/dev/null | head -1) [ -n "$RESULT_TEXT" ] && echo "$RESULT_TEXT" if [ "$RESULT_RECEIVED" = true ]; then echo "✓ Session completed (detected via stream-json)" else echo "⚠️ No result received, continuing anyway..." fi # Check for completion signals from prompt if grep -q "<promise>COMPLETE</promise>" "$TEMP_OUTPUT" 2>/dev/null; then echo "Done - all tasks complete!" exit 0 fi if grep -q "<promise>END_OF_STORY</promise>" "$TEMP_OUTPUT" 2>/dev/null; then echo "✓ Story completed, continuing to next..." fi sleep 2 done echo "Reached max iterations ($MAX_ITERATIONS)" exit 1 `` We've been running this in production for days zero freezes. Before this we had problems every other story claude code was finishing. The combination of stream-json detection + graceful kill + explicit completion signals makes it bulletproof. Hope this helps others hitting the same issue!

This is Gold! thanks so much for your detailed help above .. the equivalent to run on windows (powershell):

# "Ralph Wiggum" - iterates over specs to implement features - Using stream to show progress while in the loop
# PowerShell equivalent of minovap's bash workaround

param(
    [string]$PromptFile = "ralph/prompt.md",
    [string]$Model = "sonnet",
    [int]$MaxIterations = 999,
    [switch]$NoChrome,
    [switch]$Debug
)

$TEMP_OUTPUT = [System.IO.Path]::GetTempFileName()

# Build chrome flag
$chromeFlag = if ($NoChrome) { "" } else { "--chrome" }

# Display configuration
Write-Host ""
Write-Host "==========================================="
Write-Host "        RALPH LOOP - CLAUDE RUNNER        "
Write-Host "==========================================="
Write-Host " Prompt    : $PromptFile"
Write-Host " Model     : $Model"
Write-Host " Chrome    : $(-not $NoChrome)"
Write-Host " Max Iters : $MaxIterations"
Write-Host "==========================================="
Write-Host " Press Ctrl+C to stop"
Write-Host "==========================================="
Write-Host ""

# Validate prompt file exists
if (-not (Test-Path $PromptFile)) {
    Write-Host "ERROR: Prompt file not found: $PromptFile" -ForegroundColor Red
    exit 1
}

try {
    for ($i = 1; $i -le $MaxIterations; $i++) {
        Write-Host "==========================================="
        Write-Host " Iteration $i of $MaxIterations"
        Write-Host "==========================================="

        $PROMPT = Get-Content $PromptFile -Raw
        "" | Out-File $TEMP_OUTPUT -Encoding UTF8

        # Run with stream-json to detect completion reliably
        # Exact flags from minovap: --dangerously-skip-permissions -p "$PROMPT" --output-format stream-json --verbose
        $env:IS_SANDBOX = "1"

        $pinfo = New-Object System.Diagnostics.ProcessStartInfo
        $pinfo.FileName = "claude"
        # Use stdin for prompt (more reliable for long prompts with special chars)
        $pinfo.Arguments = "--dangerously-skip-permissions --output-format stream-json --verbose $chromeFlag --no-session-persistence --model=$Model"
        $pinfo.RedirectStandardOutput = $true
        $pinfo.RedirectStandardError = $true
        $pinfo.RedirectStandardInput = $true
        $pinfo.UseShellExecute = $false
        $pinfo.CreateNoWindow = $false

        $process = New-Object System.Diagnostics.Process
        $process.StartInfo = $pinfo
        $process.Start() | Out-Null

        # Send prompt via stdin (like piping: echo "$PROMPT" | claude ...)
        $process.StandardInput.WriteLine($PROMPT)
        $process.StandardInput.Close()

        $CLAUDE_PID = $process.Id
        $RESULT_RECEIVED = $false
        $KILLER_JOB = $null

        if ($Debug) { Write-Host "Claude PID: $CLAUDE_PID" -ForegroundColor Gray }

        # Monitor output for result message
        $lineCount = 0
        while (-not $process.StandardOutput.EndOfStream) {
            $line = $process.StandardOutput.ReadLine()
            $lineCount++

            if ($line) {
                $line | Out-File $TEMP_OUTPUT -Append -Encoding UTF8

                # Display content (stream all relevant output)
                try {
                    $json = $line | ConvertFrom-Json -ErrorAction Stop

                    # Text content streaming (delta)
                    if ($json.type -eq "content_block_delta" -and $json.delta.text) {
                        Write-Host $json.delta.text -NoNewline
                    }
                    # Assistant messages (contain full text in message.content)
                    elseif ($json.type -eq "assistant" -and $json.message.content) {
                        foreach ($block in $json.message.content) {
                            if ($block.type -eq "text" -and $block.text) {
                                Write-Host $block.text
                            }
                            elseif ($block.type -eq "tool_use") {
                                Write-Host "[TOOL: $($block.name)]" -ForegroundColor Cyan
                            }
                        }
                    }
                    # Tool use start
                    elseif ($json.type -eq "content_block_start" -and $json.content_block.type -eq "tool_use") {
                        Write-Host "`n[TOOL: $($json.content_block.name)]" -ForegroundColor Cyan
                    }
                    # Ignore system/user (conversation history)
                    elseif ($json.type -eq "system" -or $json.type -eq "user") {
                        # Skip - this is conversation history
                    }
                    # Show other types for debugging
                    else {
                        Write-Host "[$($json.type)]" -NoNewline -ForegroundColor DarkGray
                    }
                } catch {
                    # Show raw line if not JSON (first 100 chars)
                    $preview = if ($line.Length -gt 100) { $line.Substring(0,100) + "..." } else { $line }
                    Write-Host "[RAW: $preview]" -ForegroundColor DarkMagenta
                }

                if ($line -match '"type":"result"') {
                    $RESULT_RECEIVED = $true
                    # Session complete - give 2s to exit, then kill if hung
                    # Equivalent to: ( sleep 2; kill $CLAUDE_PID 2>/dev/null ) &
                    $KILLER_JOB = Start-Job -ScriptBlock {
                        param($pid)
                        Start-Sleep -Seconds 2
                        try { Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue } catch {}
                    } -ArgumentList $CLAUDE_PID
                    break
                }
            }
        }

        if ($Debug) { Write-Host "`n[DEBUG: Read $lineCount lines]" -ForegroundColor DarkGray }

        # Read any stderr
        $stderr = $process.StandardError.ReadToEnd()
        if ($stderr) {
            Write-Host "[STDERR]: $stderr" -ForegroundColor Red
        }

        # wait $CLAUDE_PID 2>/dev/null
        $process.WaitForExit(5000) | Out-Null
        if (-not $process.HasExited) {
            Write-Host "[Process still running after 5s - killing]" -ForegroundColor Yellow
            try { $process.Kill() } catch {}
        }

        # kill $KILLER_PID 2>/dev/null
        if ($KILLER_JOB) {
            Stop-Job $KILLER_JOB -ErrorAction SilentlyContinue
            Remove-Job $KILLER_JOB -ErrorAction SilentlyContinue
        }

        # Show result
        $outputContent = Get-Content $TEMP_OUTPUT -Raw -ErrorAction SilentlyContinue
        $resultLine = $outputContent -split "`n" | Where-Object { $_ -match '"type":"result"' } | Select-Object -First 1
        if ($resultLine) {
            try {
                $json = $resultLine | ConvertFrom-Json
                if ($json.result) {
                    Write-Host $json.result
                }
            } catch {}
        }

        if ($RESULT_RECEIVED) {
            Write-Host "Session completed (detected via stream-json)"
        } else {
            Write-Host "No result received, continuing anyway..."
        }

        # Check for completion signals from prompt
        if ($outputContent -match "<promise>COMPLETE</promise>") {
            Write-Host "Done - all tasks complete!"
            exit 0
        }

        if ($outputContent -match "<promise>END_OF_STORY</promise>") {
            Write-Host "Story completed, continuing to next..."
        }

        # sleep 2
        Start-Sleep -Seconds 2
    }

    Write-Host "Reached max iterations ($MaxIterations)"
    exit 1
}
finally {
    # trap "rm -f $TEMP_OUTPUT" EXIT
    Remove-Item $TEMP_OUTPUT -Force -ErrorAction SilentlyContinue
}

.\ralph\run-claude.ps1 -PromptFile "ralph/prompt.md" -Model "sonnet"

ringhamcc · 7 months ago

I had the same error, but it did not have the same root cause. The agent had not finished running when it encountered the "No messages returned" error and hung. Here is the summary of the bug that I saw:

Here's the specific timeline:                                                                         
                                                                                                        
  20:47:00 - Coder agent started for Stream 4                                                           
  20:50:02 - Last productive write: roadmap/routes/archive.py                                           
  20:52:44 - Final file written: roadmap/routes/status.py                                               
  20:52:44 → 21:29:06 - Agent hung for ~37 minutes (no files, no output)                                
  21:29:06 - Killed by me, log captured the error                                                       
                                                                                                        
  The Bug:                                                                                              
                                                                                                        
  1. Claude CLI was running the agent normally                                                          
  2. Around 20:52, the API returned "no messages"                                                       
  3. This caused an unhandled promise rejection:                                                        
  Error: No messages returned                                                                           
      at AJB (/$bunfs/root/claude:6146:78)                                                              
      at processTicksAndRejections (native:7:39)                                                        
  4. But the process didn't exit - it stayed running (we saw it in ps aux)                              
  5. It also didn't retry or log the error - it just hung silently                                      
  6. The process consumed minimal CPU (stuck at 0:43) but did nothing for 37 minutes                    
  7. Only when killed (SIGTERM/-15) did it finally write the error to the log                           
                                                                                                        
  Expected behavior: When the API returns "no messages", the CLI should either:                         
  - Retry the request                                                                                   
  - Exit with a clear error code and message                                                            
  - Not hang indefinitely with an unhandled promise rejection                                           
                                                                                                        
  The agent never got a chance to see or handle the pytest error - the API failed before/during the     
  agent's response.
ringhamcc · 7 months ago

As a workaround, I implemented similar fixes to @minovap, but I also handled resuming an incomplete task. It seems to be working well.

rezaalavi · 7 months ago

I have the same problem which makes claud cli (sdk) useless.
I haven't investigated it but it seems there is a 10 minutes timeout for 'claude -p' and if the prompt takes more than 10 minutes it stops with that error.

It could be something else but the error/hanging happens around the 10 minutes mark.

klaus7 · 7 months ago

Same problem here. Please fix.

github-actions[bot] · 6 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

github-actions[bot] · 5 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.