NousResearch/hermes-agent · error · RuntimeError
Auxiliary {task or 'call'}: LLM returned None response
Error message
Auxiliary {task or 'call'}: LLM returned None response What it means
Validation chokepoint for auxiliary responses: every successful non-streaming auxiliary LLM response passes through this validator exactly once (it is also the aux token-accounting point, issue #23270). If the response object is None — the SDK or adapter returned nothing instead of raising — the call is treated as invalid and aborted. This guards downstream code that unconditionally reads response.choices.
Source
Thrown at agent/auxiliary_client.py:8494
) -> Any:
"""Validate that an LLM response has the expected .choices[0].message shape.
Fails fast with a clear error instead of letting malformed payloads
propagate to downstream consumers where they crash with misleading
AttributeError (e.g. "'str' object has no attribute 'choices'").
See #7264.
Also the single accounting chokepoint for auxiliary usage: every
successful non-streaming aux response passes through here exactly once,
so token usage is recorded against the ambient session context published
by the agent loop (``agent.aux_accounting``, issue #23270). Recording is
best-effort and never affects validation. *provider*/*base_url* are
optional accounting hints — fallback-path calls omit them and the row
keeps the model (read from the response itself) with an empty route.
"""
if response is None:
raise RuntimeError(
f"Auxiliary {task or 'call'}: LLM returned None response"
)
from agent.aux_accounting import record_aux_usage
record_aux_usage(response, task, provider=provider, base_url=base_url)
# Allow SimpleNamespace responses from adapters (CodexAuxiliaryClient,
# AnthropicAuxiliaryClient) — they have .choices[0].message.
try:
choices = response.choices
if not choices or not hasattr(choices[0], "message"):
raise AttributeError("missing choices[0].message")
except (AttributeError, TypeError, IndexError) as exc:
recovered = _recover_aux_response_message(response)
if recovered is not None:
_record_relay_auxiliary_response_model(response)
_complete_relay_auxiliary_call()
return recovered
response_type = type(response).__name__
response_preview = str(response)[:120]View on GitHub (pinned to c896c09c42)
Solutions
- Retry the auxiliary call — a one-off None is usually a transient network/endpoint hiccup.
- Test the same provider/model with a direct curl or small script to confirm it returns a real completion body.
- If a custom base_url is in use, verify the endpoint emits a valid OpenAI chat.completion JSON body.
- If it persists, switch the auxiliary task's provider (config.yaml `auxiliary.<task>.provider`) to a known-good backend.
Defensive patterns
Strategy: retry
Type guard
def is_valid_aux_response(r) -> bool:
return r is not None and hasattr(r, "choices") and len(r.choices) > 0 Try / catch
for attempt in range(2):
try:
return await aux_call_async(...)
except RuntimeError as e:
if "LLM returned None response" in str(e) and attempt == 0:
continue # transient — retry once
raise Prevention
- Smoke-test custom endpoints with a one-shot completion before wiring them into auxiliary tasks.
- Prefer mainstream providers for auxiliary work; keep custom gateways out of the hot path until proven stable.
When it happens
Trigger: A non-streaming auxiliary call whose provider adapter/SDK returns None on transport or serialization failure instead of raising; a custom endpoint that closes the connection and the SDK swallows it into a None result.
Common situations: Flaky custom OpenAI-compatible gateway returning an empty body; an adapter (CodexAuxiliaryClient-style SimpleNamespace shim) with a bug returning None on an error branch; proxy timeouts producing empty responses.
Related errors
- Auxiliary {task or 'call'}: provider {resolved_provider} cou
- Auxiliary {task or 'call'}: LLM returned invalid response (t
- Preview mode — launching is disabled.
- no install root
- ${isPosix ? 'install.sh --manifest' : 'install.ps1 -Manifest
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/09cb26fd5e9a3ea8.
Report an issue: GitHub.