Skip to content

tunnel-client: wedges after sustained uplink flap — instant-fail loop, then a hung connect attempt with no timeout; never self-recovers without manual reconnect #1372

Description

@waleedkadous

Observed (2026-08-08, live)

Host uplink flapped ~22:02–22:04Z (independently corroborated by another workspace's monitors; link flaky since Aug 4). Afterward, with network and cloud both verified healthy (DNS resolves, https://cloud.codevos.ai answers 307 in ~420ms, reference host 200), the tunnel client:

  1. Cycled disconnected → connecting → disconnected with the transition completing in ~4ms — an instant local failure, not a real network attempt (real attempts earlier in the day took ~500ms and succeeded).
  2. At 22:06:44Z entered connecting and never transitioned again — the attempt hung with no connect timeout for 5+ minutes.
  3. Recovered instantly (connected in 600ms with existing credentials) when an empty-body POST /api/tunnel/connect ran resetCircuitBreaker() + connectTunnel() (which builds a fresh client).

Net effect for the owner: "the tower keeps disconnecting from cloud" — every uplink blip risks a permanent cloud outage until someone manually reconnects.

Code observations (lib/tunnel-client.ts)

  • handleAuthError('invalid_api_key')auth_failed → never retries (deliberate breaker). Everything that checks state === 'auth_failed' stays parked forever. If a proxy/CDN error page during a blip is ever (mis)classified as an auth error, that's a permanent stop on a transient condition. Worth verifying how close-reason parsing behaves on a Cloudflare 5xx/HTML body.
  • Transient path (scheduleReconnect + calculateBackoff) looks correct in isolation — but the observed 4ms failure loop and the final hung connecting state show some path where doConnect fails synchronously (or never completes) without a watchdog.

Asks

  1. Connect-attempt watchdog: a connecting state older than N seconds must be torn down and rescheduled — an attempt that can hang forever defeats all backoff logic.
  2. Self-healing breaker: auth_failed should half-open (retry at a long interval, e.g. 10–15 min) rather than stop forever; a genuinely revoked key just fails again cheaply.
  3. Log the failure reason on each transition (Tunnel: connecting → disconnected (reason)) — today's log has no reasons, which made this diagnosis archaeology.
  4. Consider rebuilding the client object after K consecutive instant failures (the manual recovery worked by replacing the client, suggesting wedged internal state).

Relation to #1370

#1370 covers the two same-day full deregistrations (config deleted, server-side DELETE) — a different signature, still unattributed (owner attests no dashboard click). This issue is the stays-down defect; #1370 is the credentials-vanish defect. The source-attribution logging asked there would also disambiguate here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/towerArea: Tower server / agent farm CLI

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions