Skip to content

zarvis(codex-oauth): WS keepalive pings no longer defeat the idle watchdog (fixes 91-min turn hang)#346

Merged
edwin-zvs merged 1 commit into
mainfrom
zarvis-provider-hang-timeout
Jun 6, 2026
Merged

zarvis(codex-oauth): WS keepalive pings no longer defeat the idle watchdog (fixes 91-min turn hang)#346
edwin-zvs merged 1 commit into
mainfrom
zarvis-provider-hang-timeout

Conversation

@edwin-zvs

Copy link
Copy Markdown
Contributor

Problem

While benchmarking zarvis vs codex (same gpt-5.5 backend), one task showed a 3.8×
wall-clock gap — traced to a single trial that hung for 91 minutes producing zero
model events
, then hit the harness trial timeout. The daemon log showed the adapter
alive but completely silent for 91 min, and the 90s provider idle-watchdog never fired.

Root cause: in the Responses-WebSocket path, read_ws_turn called sink.progress() on
every frame — including WS keepalive Ping control frames. The idle watchdog treats
progress() as "the turn is alive," so a stalled response stream (server holds the socket
open with periodic pings but sends no model output) reset the activity timer forever and
the turn hung for the entire trial budget. Connection-level liveness defeated the watchdog.

(Normal trials were already at parity with codex — ~11.5m vs ~10m, ~12.6s/turn vs ~13.4s.
The entire apparent gap was this one hang.)

Fix

  • read_ws_turn: count only real data (Text) frames as progress(). Ping/control
    frames no longer reset the idle watchdog, so a silent-but-pinging stream idle-times-out
    (90s) and retries/falls back instead of hanging. Real reasoning still emits Text deltas,
    so legitimate long "thinking" turns stay alive as before.
  • try_ws: bound the WS connect with WS_CONNECT_TIMEOUT (30s) — a stalled TCP /
    proxy-CONNECT / TLS handshake falls back to HTTP instead of hanging the turn.

Validation

  • New deterministic regression test ws_keepalive_pings_do_not_count_as_progress: a local
    WS server sends only Pings (no Text) → read_ws_turn records 0 progress calls (was 3),
    so the watchdog would fire instead of hanging.
  • Existing watchdog tests still green (hung_provider_turn_times_out,
    progress_pings_keep_a_text_less_stream_alive). 150 tests pass.
  • Relevant binary: agentd-adapter-zarvis.

🤖 Generated with Claude Code

… bound WS connect

A stalled Responses-WebSocket response stream (server holds the socket open with
keepalive Pings but sends no model output) defeated the 90s idle watchdog:
read_ws_turn called sink.progress() on every frame, including Ping control
frames, so the activity timer reset forever and the turn hung for the entire
trial budget (observed: a 91-min trial with zero model events — 3.8x the codex
wall-clock on the same task, entirely from this one hang).

- read_ws_turn: call sink.progress() only for real data (Text) frames, not
  Ping/control frames, so a silent-but-pinging stream idle-times-out and retries.
- try_ws: bound connect with WS_CONNECT_TIMEOUT (30s) -> fall back to HTTP on a
  stalled TCP / proxy-CONNECT / TLS handshake instead of hanging the turn.

Deterministic regression test added (local WS server sends only pings -> 0
progress calls). 150 tests pass.
@edwin-zvs
edwin-zvs merged commit e135c70 into main Jun 6, 2026
1 check passed
@edwin-zvs
edwin-zvs deleted the zarvis-provider-hang-timeout branch June 6, 2026 02:56
@edwin-zvs edwin-zvs mentioned this pull request Jun 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant