Skip to content

fix(web): unify network recovery - #985

Merged
SamSaffron merged 1 commit into
SamSaffron:mainfrom
sam-saffron-jarvis:feat/network-failure-audit
Aug 2, 2026
Merged

fix(web): unify network recovery#985
SamSaffron merged 1 commit into
SamSaffron:mainfrom
sam-saffron-jarvis:feat/network-failure-audit

Conversation

@sam-saffron-jarvis

Copy link
Copy Markdown
Contributor

What

Centralize browser network handling and make chat recovery predictable across HTTPS and WebRTC failures.

  • add a shared apiFetch layer with explicit safe-read, idempotent-mutation, non-retryable-mutation, and stream policies
  • separate authentication state from browser/backend connectivity state
  • coordinate offline/online recovery through one health and reconciliation gate
  • make both established-response and pre-response POST backoff wakeable
  • share resumable stream recovery between response streams and isolated skill runs
  • route first-party application requests through the common network layer
  • persistently renegotiate degraded WebRTC in the background while HTTPS remains usable
  • preserve ambiguous mutation outcomes rather than blindly replaying them
  • add deterministic outage, flap, retry, auth, abort, and WebRTC regression coverage

Why

The durable response stream already replayed missed events safely once a response ID existed, but failure handling around it was fragmented across feature modules.

The worst path was an initial message POST during a 20-second outage: five fast failures put it into a 60-second sleep at about 13.2 seconds, so connectivity returning at 20 seconds could still leave the message waiting roughly another 53 seconds. The initial POST now uses the same wakeable recovery mechanism as established streams and resumes immediately after coordinated health and transcript reconciliation.

The shared layer also makes the retry contract explicit: reads may retry, mutations retry only with an idempotency guarantee, and ambiguous unsafe WebRTC mutations surface an unknown outcome instead of being duplicated.

Recovery behavior

For an active response:

  1. show an explicit offline/recovering state
  2. leave the server-side response run alive
  3. suspend retries while the browser is explicitly offline
  4. on restoration, perform one shared health/reconciliation pass
  5. wake parked streams and replay from the last applied sequence
  6. recover gaps from the response snapshot

For a pre-response POST, the message body, attachments, client message ID, request ID, and idempotency key remain stable across retries. Session switches cancel the parked retry.

Review fixes

A Claude Opus review found and the final commit fixes:

  • a double-flap recovery deadlock
  • a post-recovery hot spin that could jump directly to the 60-second retry tier
  • unsafe WebRTC handling when dataChannel.send() synchronously proved no frame was sent
  • over-broad centralized 401 handling
  • healthy streams being restarted on every visibility change
  • caller aborts no longer propagating after response headers
  • misleading pending-safe and automatic-retry status text
  • unbounded/unisolated recovery hooks

Regression tests cover each path.

Tests

  • all 18 internal/serveui/static/*_test.js suites
  • go test ./internal/serveui
  • go build ./...
  • isolated go test ./...
  • go vet ./...
  • git diff --check

The 20-second outage tests cover an established response, the initial POST before x-response-id, and WebRTC HTTPS fallback/recovery. They assert immediate wake after restoration, stable idempotency, no duplicated turn, and terminal replay where applicable.

Centralize fetch retry policy, connectivity state, resumable streams, and WebRTC fallback. Wake established and pre-response retries immediately when a 20-second outage ends instead of leaving the initial POST in its 60-second slow-retry sleep.
@SamSaffron
SamSaffron merged commit de14e91 into SamSaffron:main Aug 2, 2026
1 of 2 checks passed
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.

2 participants