bug fix for hermes agent - Error: 'NoneType' object is not iterable
<p>After the latest hermes update i was getting this error:</p>
<p><!-- obsidian --></p>
<p>API call failed (attempt 1/3): TypeError<br />🔌 Provider: openai-codex Model: gpt-5.5<br />🌐 Endpoint: <a class="external-link" href="https://chatgpt.com/backend-api/codex" target="blank" rel="noopener nofollow">https://chatgpt.com/backend-api/codex</a><br />📝 Error: 'NoneType' object is not iterable<br />⚠️ Non-retryable error (HTTP None) — trying fallback...<br />❌ Non-retryable error (HTTP None): 'NoneType' object is not iterable<br />❌ Non-retryable client error (HTTP None). Aborting.<br />🔌 Provider: openai-codex Model: gpt-5.5<br />🌐 Endpoint: <a class="external-link" href="https://chatgpt.com/backend-api/codex" target="blank" rel="noopener nofollow">https://chatgpt.com/backend-api/codex</a><br />💡 This type of error won't be fixed by retrying.</p>
<p>I found the bug reported here:</p>
<p><a href="https://github.com/NousResearch/hermes-agent/issues/32894">https://github.com/NousResearch/hermes-agent/issues/32894</a></p>
<p>As the patch is not official yet, codex seemed to be able to patch it. i got codex to output a document of the changes below. </p>
<p> </p>
HTML
# Codex Responses Stream Output Fix
Date: 2026-05-27
Sensitive-data note: this write-up omits credentials, raw request data, account identifiers, local paths, and environment-specific values.
## Summary
Hermes was patched to tolerate a Codex Responses API edge case where the SDK stream parser or response convenience accessors could fail when the final response had missing, empty, or `None` output.
The visible failure was a `TypeError` like:
```text
'NoneType' object is not iterable
```
The underlying problem was that Hermes had already received useful streamed events, such as text deltas, reasoning deltas, or completed output items, but the SDK final response could still expose `output=None`, `output=[]`, or a broken `output_text` accessor. That made the conversation loop treat the response as invalid, crash while reading `output_text`, or lose streamed content that had already arrived.
## Files Patched
- `agent/codex_runtime.py`
- `agent/codex_responses_adapter.py`
- `agent/conversation_loop.py`
- `tests/run_agent/test_run_agent_codex_responses.py`
- `tests/agent/transports/test_codex_transport.py`
## Runtime Patch
### `agent/codex_runtime.py`
Added shared stream recovery helpers:
- `_backfill_codex_response_output(...)`
- `_synthesize_codex_response_from_stream(...)`
These helpers reconstruct usable response output from stream events that were already observed before the SDK returned an incomplete final response or raised a parser error.
The backfill behavior now handles three cases:
1. If completed output items were streamed, copy them onto `response.output`.
2. If text deltas were streamed and no tool calls were present, synthesize a completed assistant message output item.
3. If only reasoning deltas were streamed, synthesize a reasoning output item and mark the response incomplete so Hermes continues correctly instead of treating reasoning-only output as a final answer.
The primary stream path now collects reasoning deltas as well as completed output items and text deltas. If the SDK stream parser raises `TypeError("'NoneType' object is not iterable")`, Hermes falls back to `responses.create(stream=True)`. If that fallback also fails, Hermes can still return a synthesized incomplete response from the stream data already collected.
The fallback `create(stream=True)` path was also updated to:
- collect reasoning deltas,
- detect function-call events,
- avoid synthesizing plain text output when tool calls are involved,
- backfill terminal responses whose output is missing or empty,
- synthesize a recoverable incomplete response if the stream ends without a terminal response but useful deltas were received.
## Response Normalization Patch
### `agent/codex_responses_adapter.py`
Added `_safe_response_output_text(response)`.
The OpenAI SDK `response.output_text` convenience accessor can itself raise when the underlying output structure is malformed or missing. Hermes now reads it behind a guard:
- returns stripped text when available,
- logs a debug message when access fails,
- returns an empty string instead of letting normalization crash.
Normalization now uses this safe accessor in both places where `output_text` is used as a fallback.
The finish-reason logic was also tightened so reasoning-only responses are marked `incomplete` when there is no final visible text. This applies whether the reasoning came from raw reasoning items or extracted reasoning summary text.
## Conversation Loop Patch
### `agent/conversation_loop.py`
The validation fallback that checks `response.output_text` now catches accessor failures. This prevents the conversation loop from crashing while trying to decide whether a response with empty output can still be recovered from `output_text`.
## Tests Added
### `tests/run_agent/test_run_agent_codex_responses.py`
Added coverage for the stream parser failure path:
- primary `responses.stream(...)` emits reasoning and then raises `TypeError("'NoneType' object is not iterable")`;
- Hermes falls back to `responses.create(stream=True)`;
- fallback stream emits reasoning with a completed response whose output is `None`;
- Hermes backfills a reasoning output item instead of crashing or returning unusable output.
### `tests/agent/transports/test_codex_transport.py`
Added coverage for normalization when:
- the response contains only reasoning output,
- the SDK `output_text` property raises `TypeError("'NoneType' object is not iterable")`,
- Hermes preserves the reasoning text and marks the normalized response as `incomplete`.
## Verification
Targeted test command:
```bash
venv/bin/pytest tests/run_agent/test_run_agent_codex_responses.py tests/agent/transports/test_codex_transport.py
```
Result:
```text
112 passed, 1 warning in 27.10s
```
The warning was an unrelated Python `audioop` deprecation warning from `discord/player.py`.
## Practical Effect
After this patch, Hermes is more resilient to malformed or partially populated Codex Responses API final responses. It can recover from streamed events that were already received, avoid SDK `output_text` crashes, preserve reasoning-only responses as incomplete turns, and continue the agent loop instead of failing on a `None` output structure.
Codex Responses Stream Output Fix
Date: 2026-05-27
Sensitive-data note: this write-up omits credentials, raw request data, account identifiers, local paths, and environment-specific values.
Summary
Hermes was patched to tolerate a Codex Responses API edge case where the SDK stream parser or response convenience accessors could fail when the final response had missing, empty, or None output.
The visible failure was a TypeError like:
'NoneType' object is not iterable
The underlying problem was that Hermes had already received useful streamed events, such as text deltas, reasoning deltas, or completed output items, but the SDK final response could still expose output=None, output=[], or a broken outputtext accessor. That made the conversation loop treat the response as invalid, crash while reading outputtext, or lose streamed content that had already arrived.
Files Patched
- -
agent/codexresponsesadapter.pyagent/conversationloop.pytests/runagent/testrunagentcodexresponses.pytests/agent/transports/testcodextransport.py
agent/codexruntime.py
Runtime Patch
agent/codexruntime.py
Added shared stream recovery helpers:
- -
synthesizecodexresponsefromstream(...)
backfillcodexresponseoutput(...)
These helpers reconstruct usable response output from stream events that were already observed before the SDK returned an incomplete final response or raised a parser error.
The backfill behavior now handles three cases:
1. If completed output items were streamed, copy them onto response.output.
2. If text deltas were streamed and no tool calls were present, synthesize a completed assistant message output item.
3. If only reasoning deltas were streamed, synthesize a reasoning output item and mark the response incomplete so Hermes continues correctly instead of treating reasoning-only output as a final answer.
The primary stream path now collects reasoning deltas as well as completed output items and text deltas. If the SDK stream parser raises TypeError("'NoneType' object is not iterable"), Hermes falls back to responses.create(stream=True). If that fallback also fails, Hermes can still return a synthesized incomplete response from the stream data already collected.
The fallback create(stream=True) path was also updated to:
- - collect reasoning deltas,
- detect function-call events,
- avoid synthesizing plain text output when tool calls are involved,
- backfill terminal responses whose output is missing or empty,
- synthesize a recoverable incomplete response if the stream ends without a terminal response but useful deltas were received.
Response Normalization Patch
agent/codexresponsesadapter.py
Added saferesponseoutputtext(response).
The OpenAI SDK response.outputtext convenience accessor can itself raise when the underlying output structure is malformed or missing. Hermes now reads it behind a guard:
- - returns stripped text when available,
- logs a debug message when access fails,
- returns an empty string instead of letting normalization crash.
Normalization now uses this safe accessor in both places where outputtext is used as a fallback.
The finish-reason logic was also tightened so reasoning-only responses are marked incomplete when there is no final visible text. This applies whether the reasoning came from raw reasoning items or extracted reasoning summary text.
Conversation Loop Patch
agent/conversationloop.py
The validation fallback that checks response.outputtext now catches accessor failures. This prevents the conversation loop from crashing while trying to decide whether a response with empty output can still be recovered from outputtext.
Tests Added
tests/runagent/testrunagentcodexresponses.py
Added coverage for the stream parser failure path:
- - primary
- Hermes falls back to
responses.create(stream=True); - fallback stream emits reasoning with a completed response whose output is
None; - Hermes backfills a reasoning output item instead of crashing or returning unusable output.
responses.stream(...) emits reasoning and then raises TypeError("'NoneType' object is not iterable");
tests/agent/transports/testcodextransport.py
Added coverage for normalization when:
- - the response contains only reasoning output,
- the SDK
outputtextproperty raisesTypeError("'NoneType' object is not iterable"), - Hermes preserves the reasoning text and marks the normalized response as
incomplete.
Verification
Targeted test command:
venv/bin/pytest tests/runagent/testrunagentcodexresponses.py tests/agent/transports/testcodextransport.py
Result:
112 passed, 1 warning in 27.10s
The warning was an unrelated Python audioop deprecation warning from discord/player.py.
Practical Effect
After this patch, Hermes is more resilient to malformed or partially populated Codex Responses API final responses. It can recover from streamed events that were already received, avoid SDK outputtext crashes, preserve reasoning-only responses as incomplete turns, and continue the agent loop instead of failing on a None output structure.
