Skip to content

fix(agent): raise on empty stream instead of fabricating stop turn#38733

Open
liuhao1024 wants to merge 2 commits into
NousResearch:mainfrom
liuhao1024:fix/empty-stream-stop-turn
Open

fix(agent): raise on empty stream instead of fabricating stop turn#38733
liuhao1024 wants to merge 2 commits into
NousResearch:mainfrom
liuhao1024:fix/empty-stream-stop-turn

Conversation

@liuhao1024

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a post-loop zero-chunk guard in interruptible_streaming_api_call that raises RuntimeError when a streaming response yields no content, reasoning, tool calls, or finish_reason. This prevents the agent from silently fabricating an empty "stop" turn and looping indefinitely on empty responses.

Related Issue

Fixes #38725

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • agent/chat_completion_helpers.py: Added zero-chunk stream guard after the streaming loop exits — raises RuntimeError when finish_reason is None and all accumulators are empty, instead of falling through to finish_reason or "stop" which fabricates a successful empty turn.
  • tests/run_agent/test_streaming.py: Added test_empty_stream_raises_runtime_error to verify the guard triggers on an empty stream iterator.

How to Test

  1. Run the new test: python -m pytest tests/run_agent/test_streaming.py::TestStreamingAccumulator::test_empty_stream_raises_runtime_error -xvs
  2. Run all streaming tests to verify no regression: python -m pytest tests/run_agent/test_streaming.py -x
  3. To manually verify: point Hermes at an OpenAI-compatible endpoint that returns HTTP 200 with an empty SSE body, send a message, and confirm the agent surfaces a retryable error instead of silently looping.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Code Intelligence

  • Analyzed: interruptible_streaming_api_call in agent/chat_completion_helpers.py (called from run_agent.py streaming path)
  • Blast radius: LOW — guard only activates on empty/error streams; normal streams unaffected
  • Related patterns: _is_provider_stream_parse_error in run_agent.py handles Anthropic parse errors but has no OpenAI equivalent — this guard fills that gap for the OpenAI-wire path

When a streaming chat.completions request returns HTTP 200 but the SSE
body yields zero usable chunks (empty stream, or a non-standard error
frame the SDK doesn't surface), the streaming consumer exited its loop
with no content and finish_reason=None, then fabricated a successful
empty 'stop' turn via .  The agent accepted
the empty turn and could re-loop on a persistent condition, presenting
as a silent hang with no error surfaced.

Add a post-loop zero-chunk guard that raises RuntimeError when the
stream produced no content, reasoning, tool calls, or finish_reason.
This turns the silent-empty-turn failure mode into a normal recoverable
error that flows through the existing retry/recovery machinery.

Fixes NousResearch#38725
@sweetcornna

Copy link
Copy Markdown
Contributor

Local review pass for #38725 coverage.

Verified against commit cb5d1345254c817bcec67b2cde1886ad3fb44966:

  • /Users/cornna/project/hermes-agent/.venv/bin/python -m pytest tests/run_agent/test_streaming.py::TestStreamingAccumulator::test_empty_stream_raises_runtime_error -q -> 1 passed
  • /Users/cornna/project/hermes-agent/.venv/bin/python -m pytest tests/run_agent/test_streaming.py -x -q -> 37 passed
  • /Users/cornna/project/hermes-agent/.venv/bin/python -m ruff check agent/chat_completion_helpers.py tests/run_agent/test_streaming.py -> All checks passed
  • git diff --check origin/main...HEAD -> clean

Current GitHub checks are passing except the expected skipped merge / save-durations jobs. I do not see a blocker here.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent loop, run_agent.py, prompt builder labels Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Streaming parser silently fabricates an empty stop turn when an OpenAI-wire stream yields zero chunks

3 participants