Skip to content

fix(chat_completion_helpers): guard against OverflowError when _stale_timeout is inf in wait notice - #64924

Closed
x7peeps wants to merge 1 commit into
NousResearch:mainfrom
x7peeps:fix/chat_completion_helpers/guard-inf-deadline-in-wait-notice
Closed

fix(chat_completion_helpers): guard against OverflowError when _stale_timeout is inf in wait notice#64924
x7peeps wants to merge 1 commit into
NousResearch:mainfrom
x7peeps:fix/chat_completion_helpers/guard-inf-deadline-in-wait-notice

Conversation

@x7peeps

@x7peeps x7peeps commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Guard against OverflowError in _emit_wait_notice when the non-stream stale timeout is float('inf').

Problem

_compute_non_stream_stale_timeout returns float('inf') for local endpoints with implicit default timeouts (line 1313 in run_agent.py). The wait-notice heartbeat in interruptible_api_call does int(_deadline) to format the auto-reconnect ETA, which crashes with OverflowError: cannot convert float infinity to integer when the deadline is infinite. This turns a harmless display-layer heartbeat into an uncaught exception that can break the polling loop.

Fix

Render "no limit" instead of int(_deadline) when _deadline == float("inf"). Minimal 2-line change — only affects the display string.

…_timeout is inf in wait notice

When _compute_non_stream_stale_timeout returns float('inf') (e.g., local
endpoint with implicit default), _emit_wait_notice crashes because
int(float('inf')) raises OverflowError.

Fix: render 'no limit' instead of int(_deadline) when _deadline is inf.
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have labels Jul 15, 2026
@xxxxxeus

xxxxxeus commented Jul 15, 2026

Copy link
Copy Markdown

Confirmed this exact regression on a real Hermes installation after updating to commit 569b912d.

Environment

  • Hermes Agent: v0.18.2 (2026.7.7.2)
  • Upstream commit: 569b912d
  • OS: macOS 26.3.1 arm64
  • Python: 3.11.15
  • Endpoint: private OpenAI-compatible endpoint classified as local by is_local_endpoint()
  • Exact endpoint and routing details omitted for privacy

Observed impact

No explicit stale_timeout_seconds was configured, so the non-stream stale timeout resolved to:

float("inf")

After approximately 30 seconds without a response, the wait-state notice attempted:

int(_deadline)

and raised:

OverflowError: cannot convert float infinity to integer

This was not limited to a display problem. It interrupted the provider polling path and resulted in repeated retries, eventually producing:

API call failed after 3 retries:
cannot convert float infinity to integer

The streaming wait-notice path already guards against an infinite timeout, while the non-streaming path introduced in #64775 did not.

Also, the fail-open behavior inside _emit_wait_notice() cannot catch this exception because the f-string is evaluated before _emit_wait_notice() is called.

Local validation

I tested a local fix that safely omits the reconnect suffix for non-finite deadlines and added a regression test covering:

float("inf")

The targeted test suite passed:

18 passed in 8.33s

git diff --check also passed.

The guard in this PR fixes the observed float("inf") case. It may also be worth:

  1. adding an upstream regression test for the local/private endpoint path; and
  2. using math.isfinite() so other non-finite values cannot reach int().

Since an infinite stale timeout means there is no scheduled automatic stale reconnect, omitting the auto-reconnect at ... suffix may be clearer than displaying auto-reconnect at no limit.

Thanks for addressing this quickly.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for isolating a real regression: current main returns float("inf") for implicit local-endpoint stale timeouts (run_agent.py:1318-1322) and formats it with int(_deadline) in the non-stream heartbeat (agent/chat_completion_helpers.py:614-623).

Problems

  • The proposed message says auto-reconnect at no limit, but the reconnect branch only runs when _elapsed > _stale_timeout (agent/chat_completion_helpers.py:729-731), which never occurs for infinity.
  • == float("inf") does not protect other non-finite values. The timeout coercion accepts nan (hermes_cli/timeouts.py:4-11), and int(float("nan")) also fails.
  • No regression test accompanies the fix; the existing wait-notice test uses a finite timeout (tests/run_agent/test_wait_state_visibility.py:78-123).

Suggested changes

  • Use math.isfinite() and omit the recovery suffix when the deadline is non-finite, matching the streaming path (agent/chat_completion_helpers.py:3183-3189).
  • Add a regression test for the infinite non-stream timeout path.

Automated hermes-sweeper review.

and getattr(agent, "_codex_stream_last_event_ts", None) is None
):
_deadline = min(_deadline, _ttfb_timeout)
_deadline_text = "no limit" if _deadline == float("inf") else f"{int(_deadline)}s"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_deadline == float("inf") fixes the reported case, but use math.isfinite(_deadline) and omit the auto-reconnect suffix when it is false. nan is accepted by the current timeout coercion and also fails under int(), while an infinite stale timeout never reaches the reconnect branch.

@AntonIXO

Copy link
Copy Markdown

Confirmed the exact production signature on moa://local: failures at ~30.02s, then ~62s and ~97s after outer retries. The first failure aligns exactly with the 100 × 0.3s wait-notice heartbeat and reproduces at int(_deadline) when _compute_non_stream_stale_timeout() returns float("inf") for MoA/local endpoints.

#65594 now includes a deterministic regression test that drives the 100-poll heartbeat without sleeping 30s. It fails with the reported OverflowError before the guard and passes after it. If #64924 is preferred as the minimal implementation, that regression test should still be useful to prevent recurrence.

@teknium1

Copy link
Copy Markdown
Contributor

Superseded by #66139, which has now merged the same heartbeat crash fix with broader handling for all non-finite deadlines, accurate finite-watchdog reporting, fail-open notice construction, and full regression coverage.

Your PR identified this bug first, and that first-submitter credit is recorded in #66139. Thank you for isolating the regression.

@teknium1 teknium1 closed this Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants