Skip to content

[Bug]: Responses stream crashes when terminal response.output is null #11179

@richard950825-sys

Description

@richard950825-sys

Bug Description

Responses streaming can crash in the OpenAI SDK when an OpenAI-compatible provider emits valid response.output_item.done events, then sends a terminal response.completed whose response.output is null.

Hermes already has recovery logic for the related response.output == [] shape from Codex-style Responses streams, but this null shape fails earlier: stream.get_final_response() tries to parse the terminal response and raises before Hermes can backfill the streamed output items.

This appears to be a variant of #5879 / #5689 / #5730: same class of provider compatibility issue, but with output is None rather than an empty list.

Steps to Reproduce

  1. Configure Hermes to use an OpenAI-compatible Responses endpoint that streams output items but returns terminal response.output: null.
  2. Send a request that produces streamed output, for example a tool call.
  3. Observe stream events like:
response.created
response.in_progress
response.output_item.added
response.output_item.done
response.function_call_arguments.delta
response.function_call_arguments.done
response.completed
  1. The terminal response.completed.response.output is null.
  2. Hermes calls stream.get_final_response(), and the OpenAI SDK raises TypeError while parsing the final response.

I reproduced this against a custom OpenAI-compatible /v1/responses provider. A sanitized fake stream regression is enough to reproduce the Hermes-side failure: emit response.output_item.done, then response.completed with response.output = None, and have get_final_response() raise TypeError("'NoneType' object is not iterable").

Expected Behavior

Hermes should recover from already-streamed response.output_item.done events when the terminal response has missing/null output, just as it already recovers when the final output is an empty list.

Actual Behavior

The SDK raises before existing empty-output backfill logic can run:

TypeError: 'NoneType' object is not iterable

Observed Hermes logs include:

ERROR root: Non-retryable client error: 'NoneType' object is not iterable
WARNING root: Failed to generate context summary: 'NoneType' object is not iterable. Further summary attempts paused for 60 seconds.

A live traceback from the SDK path was:

File ".../openai/lib/streaming/responses/_responses.py", line 93, in __stream__
  self.__final_response = parse_response(...)
File ".../openai/lib/_parsing/_responses.py", line 61, in parse_response
  for output in response.output:
TypeError: 'NoneType' object is not iterable

Affected Component

  • Agent Core (conversation loop, context compression, memory)
  • Gateway (Telegram/Discord/Slack/WhatsApp)
  • Configuration (config.yaml, .env, hermes setup)

Messaging Platform (if gateway-related)

Gateway-related; observed through gateway usage. The underlying bug is in the Responses stream handling and is platform-independent.

Debug Report

Not uploaded here because the captured request dump and local debug bundle include private provider configuration and conversation payloads. The relevant sanitized stream shape, logs, and traceback are included above.

Operating System

Alibaba Cloud Linux 3 (OpenAnolis Edition), kernel 5.10.134-19.2.al8.x86_64.

Python Version

Python 3.14.3 under uv run; existing local venv is Python 3.11.13.

Hermes Version

Tested from current origin/main at commit 37913d91.

hermes version output in the local environment:

Hermes Agent v0.9.0 (2026.4.13)
Python: 3.14.3
OpenAI SDK: 2.24.0

Additional Logs / Traceback (optional)

Non-retryable error (HTTP None): 'NoneType' object is not iterable
Non-retryable client error: 'NoneType' object is not iterable
Failed to generate context summary: 'NoneType' object is not iterable

Root Cause Analysis (optional)

The existing recovery logic collects response.output_item.done and text deltas, then backfills the final response only when get_final_response() returns a response object with output == [].

For providers that send terminal response.output = null, the OpenAI SDK parser raises inside get_final_response() while iterating response.output, so Hermes never reaches the existing empty-list recovery path. The same issue can affect both the main agent stream path in run_agent.py and the auxiliary Codex/Responses adapter in agent/auxiliary_client.py.

Proposed Fix (optional)

Recover generically from the stream events rather than provider-specific behavior:

  • Capture terminal response.completed / response.incomplete / response.failed response objects from the event stream before calling get_final_response().
  • If terminal output is missing/null/empty and streamed response.output_item.done events were collected, backfill response.output from those items.
  • If no output items exist but text deltas were collected and no tool calls were streamed, synthesize a message output from text deltas.
  • Catch the specific SDK TypeError("'NoneType' object is not iterable") final-parse failure only when streamed output exists, then recover through the same backfill path.
  • Add fake-stream regressions for both the main agent stream and auxiliary client paths.

I am opening a PR with this fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existscomp/agentCore agent loop, run_agent.py, prompt buildertype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions