Skip to content

fix(engine,web): F2 pong reports sent position + watchdog two-strike (WI-126) - #670

Closed
thedancingdeveloper wants to merge 1 commit into
feat/terminal-attach-f3-lazy-cache-replayfrom
feat/terminal-attach-f2-pong-race
Closed

thedancingdeveloper wants to merge 1 commit into
feat/terminal-attach-f3-lazy-cache-replayfrom
feat/terminal-attach-f2-pong-race

Conversation

@thedancingdeveloper

Copy link
Copy Markdown
Contributor

Fourth PR of the terminal-attach-budget initiative (WI-121). Stacked on #669 (F3) so the diff is F2-only; all land on feat/terminal-attach-budget.

The bug (WI-126)

A bursty session could trip a spurious reconnect. The inbound task answered a ping with scrollback_position() (total_written) and handed the pong to the outbound task on a separate channel; select! could send that pong before the broadcast chunks it referenced, so the client briefly saw serverPos > localPos, printed [disconnected], and reattached — replaying scrollback for no reason.

The fix

Engine (ws.rs) — the ping id is forwarded to the outbound task, which answers with sent_pos (the offset actually written to this socket) after flushing anything already queued (flush_available). The pong is ordered after those chunks on the wire, so its pos can never exceed what the client has received. docs/ENGINE.md documents the pong.pos semantics.

Web (terminalWatchdog.ts) — a single behind pong is a suspect, not a verdict: it arms a prompt confirm probe instead of recycling. Only a second behind pong within WATCHDOG_TIMEOUT_MS, with no output in between, is a real stall; any output clears the suspicion.

Note: the plan said "terminalWatchdog has no tests today" — it already had terminalWatchdog.test.ts. Extended it rather than creating one.

Tests

  • engine pong_never_reports_the_server_ahead_of_this_socket: ping mid-stream under load, assert pong.pos ≤ snapshot_end + live bytes received on the socket.
  • web: five two-strike cases (suspect; confirm→recycle; output clears; caught-up clears; stale-suspect drop).

Verification

  • cargo test -p vogt-engine-server --test integration105 passed; fmt/clippy clean.
  • pnpm typecheck clean; pnpm vitest913 passed; scripts/check_docs.py clean.

The plan's stated acceptance ("no [disconnected] during H3's steady-state phase") lands with the H3 live spec (WI-124).

🤖 Generated with Claude Code

https://claude.ai/code/session_01UtEFLJAhLiq4NfZCN2vuBb

The liveness probe could force a spurious reconnect on a bursty session. The
inbound task answered a ping with scrollback_position() (total_written) and
handed the pong to the outbound task on a separate channel; select! could send
that pong before the broadcast chunks it referenced, so the client briefly saw
serverPos > localPos, printed [disconnected], and reattached.

Engine (ws.rs): the ping id is forwarded to the outbound task, which answers
with sent_pos — the offset actually written to this socket — after flushing
anything already queued (new flush_available helper). The pong is therefore
ordered after those chunks on the wire and its pos can never exceed what the
client has received. ENGINE.md documents the pong pos semantics.

Web (terminalWatchdog.ts): a single behind pong is a suspect, not a verdict —
it arms a prompt confirm probe rather than recycling. Only a second behind pong
within WATCHDOG_TIMEOUT_MS, with no output in between, is treated as a real
stall; any output clears the suspicion. (terminalWatchdog.test.ts already
existed, contrary to the plan note; extended with the two-strike cases.)

Tests:
- engine pong_never_reports_the_server_ahead_of_this_socket: under load, ping
  mid-stream and assert pong.pos <= snapshot_end + live bytes received on the
  socket.
- web: five two-strike watchdog cases (suspect, confirm-recycle, output clears,
  caught-up clears, stale-suspect drop).

Verification: cargo test -p vogt-engine-server --test integration 105 passed;
fmt + clippy clean; web typecheck clean; pnpm vitest 913 passed;
scripts/check_docs.py clean. WI-126.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtEFLJAhLiq4NfZCN2vuBb
@thedancingdeveloper

Copy link
Copy Markdown
Contributor Author

Cleanup: superseded by the single promotion PR #676. All of this work is already integrated into feat/terminal-attach-budget and is reviewable there per-commit (each commit is one initiative item with a full message). Closing to keep the initiative to one PR; the commit is preserved.

@thedancingdeveloper
thedancingdeveloper deleted the feat/terminal-attach-f2-pong-race branch September 9, 2026 20:59
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