Bug Description
When using the openai-codex provider with gpt-5.4, Hermes fails with RuntimeError: Responses API returned no output items even though the streaming API delivers text via output_text.delta events. The final response.output list is empty, but the model IS responding — the text arrives through streaming events.
This causes:
- All 3 retries to fail with the same error
- Each retry's streaming text being concatenated and shown as a single garbled response (e.g.
hellohellohello)
Steps to Reproduce
- Configure
openai-codex provider with gpt-5.4 via OAuth
- Run
hermes chat -q "say hello"
- Observe 3 retries each with
Invalid API response: response.output is empty
Expected Behavior
Hermes should use the text received via streaming output_text.delta events as the response content when response.output is empty but status == 'completed'.
Actual Behavior
⚠️ Invalid API response (attempt 1/3): response.output is empty
⚠️ Invalid API response (attempt 2/3): response.output is empty
⚠️ Invalid API response (attempt 3/3): response.output is empty
❌ Max retries (3) exceeded for invalid responses. Giving up.
hellohellohello ← concatenated text from all 3 retry streaming events
Error: Invalid API response shape. Likely rate limited or malformed provider response.
Root Cause Analysis
The Codex API (via https://chatgpt.com/backend-api/codex) returns streaming output_text.delta events with text, but the final response.output list is empty ([]). The response has status='completed' and non-zero usage.output_tokens.
Two places raise errors:
_normalize_codex_response (run_agent.py ~line 3447):
if not isinstance(output, list) or not output:
raise RuntimeError("Responses API returned no output items")
- Validation code (~line 7368):
elif len(output_items) == 0:
response_invalid = True
error_details.append("response.output is empty")
Both assume output[] will always contain the response, but the Codex API now delivers text via streaming events only, leaving output[] empty.
Proposed Fix
In _run_codex_stream(), accumulate output_text.delta events and store as self._last_stream_accumulated_text. Then in _normalize_codex_response(), use this as a fallback to synthesize a message item when output[] is empty:
# _normalize_codex_response
if not isinstance(output, list) or not output:
accumulated = getattr(self, "_last_stream_accumulated_text", None)
if accumulated:
synthetic_item = SimpleNamespace(
type="message",
content=[SimpleNamespace(type="output_text", text=accumulated)],
role="assistant",
)
output = [synthetic_item]
else:
raise RuntimeError("Responses API returned no output items")
Affected Component
- Gateway (Telegram/Discord/Slack/WhatsApp)
- Agent Core (conversation loop, context compression, memory)
Messaging Platform
Operating System
macOS 25.4.0 (Darwin)
Python Version
3.11.15
Hermes Version
v0.7.0 (2026.4.3)
Relevant Logs
WARNING root: Invalid API response (retry 1/3): response.output is empty | Provider: model=gpt-5.4
WARNING root: Invalid API response (retry 2/3): response.output is empty | Provider: model=gpt-5.4
ERROR root: Invalid API response after 3 retries.
DEBUG root: API Response received - Model: gpt-5.4, Usage: ResponseUsage(input_tokens=12716, output_tokens=23, reasoning_tokens=14)
DEBUG root: response.output_text='', status='completed', reasoning=Reasoning(effort='medium', summary='detailed')
Bug Description
When using the
openai-codexprovider withgpt-5.4, Hermes fails withRuntimeError: Responses API returned no output itemseven though the streaming API delivers text viaoutput_text.deltaevents. The finalresponse.outputlist is empty, but the model IS responding — the text arrives through streaming events.This causes:
hellohellohello)Steps to Reproduce
openai-codexprovider withgpt-5.4via OAuthhermes chat -q "say hello"Invalid API response: response.output is emptyExpected Behavior
Hermes should use the text received via streaming
output_text.deltaevents as the response content whenresponse.outputis empty butstatus == 'completed'.Actual Behavior
Root Cause Analysis
The Codex API (via
https://chatgpt.com/backend-api/codex) returns streamingoutput_text.deltaevents with text, but the finalresponse.outputlist is empty ([]). The response hasstatus='completed'and non-zerousage.output_tokens.Two places raise errors:
_normalize_codex_response(run_agent.py ~line 3447):Both assume
output[]will always contain the response, but the Codex API now delivers text via streaming events only, leavingoutput[]empty.Proposed Fix
In
_run_codex_stream(), accumulateoutput_text.deltaevents and store asself._last_stream_accumulated_text. Then in_normalize_codex_response(), use this as a fallback to synthesize a message item whenoutput[]is empty:Affected Component
Messaging Platform
Operating System
macOS 25.4.0 (Darwin)
Python Version
3.11.15
Hermes Version
v0.7.0 (2026.4.3)
Relevant Logs