Skip to content

feat(workspace-client,desktop): relay WS outage telemetry + version-stamped desktop events - #5653

Merged
Kitenite merged 3 commits into
mainfrom
remote-workspace-disconne
Jul 13, 2026
Merged

Kitenite merged 3 commits into
mainfrom
remote-workspace-disconne

Conversation

@Kitenite

@Kitenite Kitenite commented Jul 13, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Add outage-episode telemetry to createRelaySocket (the eventBus / workspace "disconnected" surface, previously silent): relay_ws_access_denied, relay_ws_degraded, and relay_ws_recovered PostHog events, at most one per outage plus one on recovery.
  • Register app_version as a PostHog super property in the desktop renderer so every event (including the existing terminal_connect_failed) is attributable to the build that emitted it.

Why / Context

Investigating the remote-workspace disconnect reports (relay 401 redial loop, fixed by #5628/#5637 but not yet released) hit two observability gaps:

  1. terminal_connect_failed events carry no app version — once 1.14.4 ships there's no way to segment pre/post-fix builds and confirm the 401 curve dies. The person-profile desktop_version reflects the current install, not the build at event time.
  2. The eventBus relay socket that drives workspace "disconnected" state emits no telemetry at all, so access-denial episodes (the v2Hosts/v2UsersHosts class of failure) and outage durations are invisible in production.

How It Works

  • outageReporter.ts (co-located in relaySocket/) is a pure state machine: callers feed it dial outcomes (attempt, accessDenied, failed, opened) and it collapses connection churn into episodes — one access_denied (preflight 403) or degraded (≥5 consecutive failed dials) event per episode, one recovered with outage_ms and attempt count when the socket reconnects. partysocket's retryCount only resets after 5s of stable uptime, so flapping accumulates into one episode instead of spamming pairs.
  • The sink is process-wide (setRelaySocketTelemetry), keeping @superset/workspace-client analytics-vendor-free; the desktop renderer wires it to PostHog in initPostHog. Web consumers are unaffected (sink defaults to null).
  • Sockets carry a name label (event-bus, web-terminal) so consumers are distinguishable in queries. The reported endpoint is the attempt URL minus its query string — the token never leaves the module.

Manual QA Checklist

  • Desktop dev app: events carry app_version (CDP-verified: super property registered, 1.14.1 in dev)
  • Outage cycle CDP-verified end-to-end: dead endpoint → one relay_ws_degraded after 5 failed dials → healed endpoint → one relay_ws_recovered with sane outage_ms; access-denied and terminal give-up paths verified too (see verification comment)

Testing

  • bun run typecheck (workspace-client, desktop, web)
  • bun run lint (exits 0)
  • bun test in packages/workspace-client — 18 pass: new pure unit tests for the reporter policy (threshold boundary, once-per-episode dedup, silent short blips, episode reset on regrant, token stripping) plus WS integration tests proving the socket wiring end-to-end against a scriptable local relay
  • Mutation-verified: removing the degraded emission and breaking the access-denied dedup each made exactly the expected test fail

Design Decisions

  • Module-level sink instead of threading a callback through RelaySocketOptions: every getEventBus call site would need plumbing for no gain; a process-wide analytics hook is the same shape posthog itself uses.
  • Once-per-episode emission instead of per-attempt: the eventBus 403 path re-probes every 5 minutes indefinitely — per-attempt events would flood; episode + recovery events preserve the debugging signal (cause, duration, attempt count) at bounded volume.

Known Limitations

  • terminal_connect_failed in the desktop terminal transport keeps its own capture path; it now gains app_version via the super property but doesn't use the outage reporter (it has its own max-attempts/give-up semantics). Unifying them is possible follow-up.

Open in Stage

Summary by cubic

Adds relay WebSocket outage telemetry and version-stamped desktop analytics to improve visibility into remote workspace disconnects. Emits relay_ws_* events once per outage and records app_version on every desktop event; fixes attempt counting and filters synthetic closes for accurate stats.

  • New Features
    • @superset/workspace-client: added outage reporter and process-wide telemetry sink (setRelaySocketTelemetry, RelaySocketTelemetryEvent). createRelaySocket now reports access_denied, degraded (≥5 failed dials), and recovered with outage_ms and attempt count; tokens are stripped from endpoints. Hardened reporting: correct attempt ordinals, ignore partysocket synthetic closes, include last close info when errors cross the threshold, and guard the sink with try/catch.
    • Socket labeling: new name option on createRelaySocket; event bus uses event-bus, web terminal uses web-terminal.
    • Desktop: PostHog sink forwards relay_ws_${kind} events with endpoint, region, close info, and attempt counts. Registers app_version as a super property so all desktop events are attributed to the emitting build.

Written for commit fc1911c. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added relay connection health telemetry for outages, access-denied episodes, and recovery, including endpoint context and outage duration.
    • Desktop analytics now forward relay socket outage/recovery signals to PostHog and include the application version.
    • Web terminal and event-bus relay connections now include identifiable connection labels for clearer attribution.
  • Bug Fixes
    • Improved reliability of relay outage telemetry emission, ensuring telemetry failures don’t affect socket behavior.
  • Tests
    • Expanded telemetry and outage episode coverage, including threshold crossing, recovery, access-denied semantics, and data privacy (no token leakage).

…tamped desktop events

Production terminal_connect_failed events showed the expired-JWT redial
loop but couldn't be attributed to app builds, and the eventBus relay
socket (the workspace "disconnected" surface) emitted no telemetry at
all. This adds:

- outageReporter: pure episode state machine emitting access_denied /
  degraded / recovered at most once per outage, via a pluggable
  process-wide sink (package stays analytics-vendor-free)
- createRelaySocket wires dial outcomes into the reporter; sockets are
  labeled (event-bus, web-terminal) for queryability
- desktop registers app_version as a PostHog super property and
  forwards relay socket health to relay_ws_* events
@ghost

ghost commented Jul 13, 2026 •

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 5 individual chapters for you:

Title
1 Register app_version as a telemetry super property
2 Implement the outage reporter state machine
3 Wire outage reporting into createRelaySocket
4 Export telemetry utilities from workspace-client
5 Label socket instances and wire desktop sink
Open in Stage

Chapters generated by Stage for commit fc1911c on Jul 13, 2026 6:10am UTC.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Relay socket connections now track outage episodes, emit sanitized telemetry for degradation, access denial, and recovery, and expose socket names for reporting. Desktop PostHog wiring captures these events and registers the application version.

Changes

Relay socket telemetry

Layer / File(s) Summary
Outage episode reporting
packages/workspace-client/src/lib/relaySocket/outageReporter.ts, packages/workspace-client/src/lib/relaySocket/outageReporter.test.ts
Adds telemetry contracts, a global sink, per-socket outage tracking, thresholded degradation events, access-denied events, recovery events, and focused tests.
Relay socket lifecycle integration
packages/workspace-client/src/lib/relaySocket/relaySocket.ts, packages/workspace-client/src/lib/eventBus.ts, apps/web/src/app/workspaces/.../TerminalConnection.ts, packages/workspace-client/src/lib/relaySocket/relaySocket.test.ts
Labels event-bus and web-terminal sockets and reports dial attempts, failures, access denial, and successful opens through the outage reporter.
Telemetry exports and desktop analytics
packages/workspace-client/src/lib/relaySocket/index.ts, packages/workspace-client/src/index.ts, apps/desktop/src/renderer/lib/posthog.ts
Re-exports telemetry APIs, captures relay events in PostHog, and registers the desktop application version.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RelaySocket
  participant OutageReporter
  participant PostHog
  RelaySocket->>OutageReporter: report dial, failure, denial, or open
  OutageReporter->>OutageReporter: aggregate outage episode
  OutageReporter->>PostHog: capture relay_ws_<kind> event
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately summarizes the main change: relay WebSocket outage telemetry plus desktop event version stamping.
Description check ✅ Passed The description is detailed and covers summary, rationale, implementation, QA, testing, and notes, though it omits explicit Related Issues and Type of Change sections.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch remote-workspace-disconne

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds telemetry for relay WebSocket outages and stamps desktop analytics with the app version. The main changes are:

  • New relay outage reporter for access-denied, degraded, and recovered episodes.
  • Desktop PostHog wiring for relay telemetry and app_version super properties.
  • Socket labels for event-bus and web-terminal consumers.
  • Unit and integration tests for outage reporting behavior.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/lib/posthog.ts Registers desktop app version analytics and installs the relay socket telemetry sink.
packages/workspace-client/src/lib/relaySocket/outageReporter.ts Adds the outage episode state machine and telemetry event shape.
packages/workspace-client/src/lib/relaySocket/relaySocket.ts Connects relay socket attempts, failures, access denials, and opens to the outage reporter.
packages/workspace-client/src/index.ts Exports the new relay socket telemetry APIs from the package entrypoint.
packages/workspace-client/src/lib/relaySocket/index.ts Re-exports outage reporter telemetry APIs from the relay socket module.
packages/workspace-client/src/lib/eventBus.ts Labels event-bus relay sockets for telemetry attribution.
apps/web/src/app/workspaces/[workspaceId]/components/WebTerminal/TerminalConnection.ts Labels web-terminal relay sockets for telemetry attribution.

Reviews (1): Last reviewed commit: "feat(workspace-client,desktop): relay WS..." | Re-trigger Greptile

@github-actions

github-actions Bot commented Jul 13, 2026 •

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
apps/desktop/src/renderer/lib/posthog.ts (1)

37-48: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Wrap the telemetry callback in a try-catch to protect the relay socket lifecycle.

If posthogFull.capture throws, the error propagates through telemetrySink → emit → accessDenied/failed/opened → the WebSocket event handlers or the async provider in relaySocket.ts. In the accessDenied path specifically, this would prevent opts.onAccessDenied?.() from being called and prevent the intended throw new Error("relay access denied") from executing, altering the connection shutdown behavior.

🛡️ Proposed defensive wrapper
 	setRelaySocketTelemetry((event) => {
-		posthogFull.capture(`relay_ws_${event.kind}`, {
-			socket_name: event.socketName,
-			endpoint: event.endpoint,
-			preflight_status: event.preflightStatus,
-			tunnel_region: event.tunnelRegion,
-			close_code: event.closeCode,
-			close_reason: event.closeReason,
-			reconnect_attempts: event.failedAttempts,
-			outage_ms: event.outageMs,
-		});
+		try {
+			posthogFull.capture(`relay_ws_${event.kind}`, {
+				socket_name: event.socketName,
+				endpoint: event.endpoint,
+				preflight_status: event.preflightStatus,
+				tunnel_region: event.tunnelRegion,
+				close_code: event.closeCode,
+				close_reason: event.closeReason,
+				reconnect_attempts: event.failedAttempts,
+				outage_ms: event.outageMs,
+			});
+		} catch {
+			// Telemetry must never disrupt the relay socket lifecycle.
+		}
 	});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/lib/posthog.ts` around lines 37 - 48, Wrap the
callback registered with setRelaySocketTelemetry in a try-catch so failures from
posthogFull.capture do not propagate into relay socket lifecycle handlers. Keep
the existing event payload and ensure the caught telemetry error is handled
without interrupting accessDenied, failed, or opened processing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/desktop/src/renderer/lib/posthog.ts`:
- Around line 37-48: Wrap the callback registered with setRelaySocketTelemetry
in a try-catch so failures from posthogFull.capture do not propagate into relay
socket lifecycle handlers. Keep the existing event payload and ensure the caught
telemetry error is handled without interrupting accessDenied, failed, or opened
processing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9b6320bc-484a-4cdf-a364-1e5ea0de7d7e

📥 Commits

Reviewing files that changed from the base of the PR and between 70072a9 and 8f9a826.

📒 Files selected for processing (9)
  • apps/desktop/src/renderer/lib/posthog.ts
  • apps/web/src/app/workspaces/[workspaceId]/components/WebTerminal/TerminalConnection.ts
  • packages/workspace-client/src/index.ts
  • packages/workspace-client/src/lib/eventBus.ts
  • packages/workspace-client/src/lib/relaySocket/index.ts
  • packages/workspace-client/src/lib/relaySocket/outageReporter.test.ts
  • packages/workspace-client/src/lib/relaySocket/outageReporter.ts
  • packages/workspace-client/src/lib/relaySocket/relaySocket.test.ts
  • packages/workspace-client/src/lib/relaySocket/relaySocket.ts

@cubic-dev-ai cubic-dev-ai Bot 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.

2 issues found across 9 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/workspace-client/src/lib/relaySocket/relaySocket.ts">

<violation number="1" location="packages/workspace-client/src/lib/relaySocket/relaySocket.ts:65">
P3: `relay_ws_access_denied` reports zero attempts on the first denied dial, so denial telemetry undercounts the episode that triggered it. Pass the current dial as `retryCount + 1` here; later retries have the same off-by-one before provider rejection is handled.</violation>

<violation number="2" location="packages/workspace-client/src/lib/relaySocket/relaySocket.ts:87">
P2: The `error` event listener on the socket fires before the `close` event listener for abnormal connection drops (per WebSocket spec, error fires first). Since both handlers call `reporter.failed()`, the error handler fires first, marks the outage as reported, and emits a `degraded` event with `closeCode: null`. When the `close` handler fires moments later with a meaningful code (e.g., 1006) and reason, it finds `reported = true` and returns early, discarding the diagnostic details. This reduces the telemetry value for connection drops, especially for differentiating abnormal closures from silent failures. Consider having the close handler be the primary signal (passing full `CloseInfo`) and the error handler be a fallback that only calls `reporter.failed()` if `reported` is still false after a short microtask delay, or restructure the reporter to accept optional close info that supersedes the error-only call.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Renderer as Desktop Renderer (initPostHog)
    participant PostHog as PostHog (capture)
    participant TelemSink as Telemetry Sink (setRelaySocketTelemetry)
    participant EventBus as EventBus (getOrCreateConnection)
    participant CreateSocket as createRelaySocket (with name)
    participant Reporter as outageReporter (state machine)
    participant Relay as Remote Relay (WS / _whoowns)

    Note over Renderer, Relay: NEW: Relay WS telemetry + version-stamped events

    Renderer->>PostHog: register({app_version, app_name, platform})
    Note over Renderer,PostHog: CHANGED: app_version super property added

    Renderer->>TelemSink: setRelaySocketTelemetry(sinkFn)
    Note over Renderer,TelemSink: Process-wide sink, forwards to PostHog

    EventBus->>CreateSocket: createRelaySocket(name="event-bus", ...)
    CreateSocket->>Reporter: createOutageReporter("event-bus")

    Note over CreateSocket, Relay: --- Degraded episode (≥5 failed dials) ---

    CreateSocket->>Relay: attempt(url+signedToken)
    CreateSocket->>Reporter: attempt(url, probe)
    CreateSocket->>Relay: _whoowns preflight
    Relay-->>CreateSocket: 200 OK (or null)
    CreateSocket->>Reporter: failed(attempts=1)
    loop Failed dials 2..4
        CreateSocket->>Reporter: failed(attempts)
    end
    CreateSocket->>Reporter: failed(attempts=5)
    alt attempts >= DEGRADED_AFTER_ATTEMPTS (5)
        Reporter->>TelemSink: emit({kind:"degraded", socketName:"event-bus", failedAttempts:5, ...})
        TelemSink->>PostHog: capture("relay_ws_degraded", { ... })
    end

    CreateSocket->>Reporter: failed(attempts=6)  -- dedup, no emission

    Note over CreateSocket, Relay: --- Recovery ---

    Relay-->>CreateSocket: connect success
    CreateSocket->>Reporter: opened(attempts=7)
    alt was reported (degraded)
        Reporter->>TelemSink: emit({kind:"recovered", outageMs, failedAttempts:7})
        TelemSink->>PostHog: capture("relay_ws_recovered", { ... })
    end

    Note over CreateSocket, Relay: --- Access denied episode ---

    CreateSocket->>Relay: attempt(url+signedToken)
    CreateSocket->>Reporter: attempt(url, probe)
    CreateSocket->>Relay: _whoowns preflight
    Relay-->>CreateSocket: 403 Forbidden (preflight_status:403)
    CreateSocket->>Reporter: accessDenied(attempts=0)
    alt not yet reported
        Reporter->>TelemSink: emit({kind:"access_denied", preflightStatus:403, ...})
        TelemSink->>PostHog: capture("relay_ws_access_denied", { ... })
    end
    CreateSocket->>Reporter: failed(attempts=1)  -- no degraded emission, suppressed
    CreateSocket->>Reporter: accessDenied(attempts=2) -- dedup, no emission
    CreateSocket->>Reporter: accessDenied(attempts=3) -- dedup

    Note over CreateSocket, Relay: --- Regrant ends episode ---

    Relay-->>CreateSocket: 200 OK + connect success
    CreateSocket->>Reporter: opened(attempts=10)
    Reporter->>TelemSink: emit({kind:"recovered", outageMs, ...})
    TelemSink->>PostHog: capture("relay_ws_recovered", { ... })

    Note over Renderer, PostHog: Existing events (terminal_connect_failed) now carry app_version via super property
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

maxEnqueuedMessages: opts.maxEnqueuedMessages ?? 0,
});

socket.addEventListener("close", (event) => {

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.

P2: The error event listener on the socket fires before the close event listener for abnormal connection drops (per WebSocket spec, error fires first). Since both handlers call reporter.failed(), the error handler fires first, marks the outage as reported, and emits a degraded event with closeCode: null. When the close handler fires moments later with a meaningful code (e.g., 1006) and reason, it finds reported = true and returns early, discarding the diagnostic details. This reduces the telemetry value for connection drops, especially for differentiating abnormal closures from silent failures. Consider having the close handler be the primary signal (passing full CloseInfo) and the error handler be a fallback that only calls reporter.failed() if reported is still false after a short microtask delay, or restructure the reporter to accept optional close info that supersedes the error-only call.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/workspace-client/src/lib/relaySocket/relaySocket.ts, line 87:

<comment>The `error` event listener on the socket fires before the `close` event listener for abnormal connection drops (per WebSocket spec, error fires first). Since both handlers call `reporter.failed()`, the error handler fires first, marks the outage as reported, and emits a `degraded` event with `closeCode: null`. When the `close` handler fires moments later with a meaningful code (e.g., 1006) and reason, it finds `reported = true` and returns early, discarding the diagnostic details. This reduces the telemetry value for connection drops, especially for differentiating abnormal closures from silent failures. Consider having the close handler be the primary signal (passing full `CloseInfo`) and the error handler be a fallback that only calls `reporter.failed()` if `reported` is still false after a short microtask delay, or restructure the reporter to accept optional close info that supersedes the error-only call.</comment>

<file context>
@@ -74,5 +83,15 @@ export function createRelaySocket(opts: RelaySocketOptions): RelaySocket {
 		maxEnqueuedMessages: opts.maxEnqueuedMessages ?? 0,
 	});
+
+	socket.addEventListener("close", (event) => {
+		// 1000 = deliberate close (cleanup, access-denied shutdown), not a failure.
+		if (event.code !== 1000) {
</file context>

Comment thread packages/workspace-client/src/lib/relaySocket/relaySocket.ts Outdated
…ket synthetic closes, harden sink

Review fixes + CDP-verified fixes:
- failuresSoFar() passes retryCount+1 so the first denied/failed dial
  counts itself (cubic P3)
- keep the outage's last observed CloseInfo so degraded isn't emitted
  with null close details when the error listener crosses the
  threshold (cubic P2)
- wrap the telemetry sink in try/catch inside the reporter so a
  throwing sink can never break the socket lifecycle (coderabbit)
- ignore partysocket synthetic close events (browser cloneEvent mangles
  them: code becomes the string "close"); found live via CDP — a
  deliberate close() after >=4 failures in the uptime window emitted a
  spurious degraded, and close_code/close_reason carried garbage
@Kitenite

Copy link
Copy Markdown
Member Author

Addressed all review findings in fc1911c, then verified the full telemetry surface live over CDP against the dev app (with #5652 merged in):

Review dispositions

  • cubic P3 (off-by-one) — fixed: failuresSoFar() passes retryCount + 1; confirmed live (relay_ws_access_denied now reports reconnect_attempts: 1 on the first denied dial).
  • cubic P2 (degraded loses close info) — fixed in substance: the reporter keeps the outage's last observed CloseInfo, so whichever listener crosses the threshold attaches it. (The claimed error-before-close ordering is actually inverted in partysocket — its _handleError dispatches a synthetic close first — but the loss was real.)
  • coderabbit (try/catch around capture) — fixed at the reporter seam instead of the desktop sink: emit wraps the sink in try/catch, protecting the socket lifecycle from any sink, not just PostHog's. Unit-tested with a throwing sink.

Found and fixed a real bug during CDP verification (browser-only, invisible to bun tests which take partysocket's Node cloneEvent path): partysocket's browser cloneEvent mangles its own synthetic close events (event.code becomes the string "close", event.reason the original event object). Consequences before the fix: a deliberate close(1000) slipped past the 1000 filter and, after ≥4 failures in the same uptime window, emitted a spurious relay_ws_degraded; close_code/close_reason could carry garbage. The close listener now only counts real numeric-code closes — synthetic closes either duplicate an error event (already counted) or signal deliberate teardown.

CDP verification (all against the wired production code path, patched posthog.capture recording every event)

  • app_version registered as a super property (1.14.1 in dev) ✅
  • relay_ws_access_denied: exactly one event per denial episode via a local 403 _whoowns stub (preflight_status: 403, socket closed permanently) ✅
  • relay_ws_degraded → relay_ws_recovered: dead port → 5 failed dials → degraded; endpoint healed → recovered with sane outage_ms (321ms); deliberate close afterwards emits nothing ✅
  • terminal_connect_failed: forced give-up on a live terminal (close_code 1006, attempts 10, token-free endpoint), feat(desktop): terminal connection status indicator with diagnosis popover #5652's red "Disconnected" indicator rendered, terminal restored cleanly after ✅

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
packages/workspace-client/src/lib/relaySocket/outageReporter.test.ts (1)

70-82: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

failed(6) never reaches the throwing sink in this test.

accessDenied(1) sets reported = true before failed(6) is called, so failed hits the early return at if (reported || ...) return without invoking emit. The assertion expect(() => reporter.failed(6)).not.toThrow() passes trivially — the throwing sink is never exercised through the failed → emit path. If the try/catch in emit were removed, this test would still pass for failed.

Use a fresh reporter so failed crosses the threshold with reported === false and actually calls the sink:

🛠️ Proposed fix
 it("never lets a throwing sink escape into the caller", () => {
   setRelaySocketTelemetry(() => {
     throw new Error("analytics exploded");
   });
   const reporter = createOutageReporter("bus");
   reporter.attempt("ws://relay/hosts/h/events", {
     status: 403,
     region: null,
   });
   expect(() => reporter.accessDenied(1)).not.toThrow();
-  expect(() => reporter.failed(6)).not.toThrow();
   expect(() => reporter.opened(7)).not.toThrow();
+
+  const failedReporter = createOutageReporter("bus");
+  failedReporter.attempt("ws://relay/hosts/h/events", null);
+  expect(() => failedReporter.failed(6)).not.toThrow();
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workspace-client/src/lib/relaySocket/outageReporter.test.ts` around
lines 70 - 82, Update the throwing-sink test around createOutageReporter so
failed(6) is invoked on a fresh reporter whose reported state is still false and
whose failure count crosses the emission threshold, ensuring it reaches emit.
Keep the existing non-throwing assertions for accessDenied, failed, and opened
while exercising the failed-to-sink path rather than the early return.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/workspace-client/src/lib/relaySocket/outageReporter.test.ts`:
- Around line 70-82: Update the throwing-sink test around createOutageReporter
so failed(6) is invoked on a fresh reporter whose reported state is still false
and whose failure count crosses the emission threshold, ensuring it reaches
emit. Keep the existing non-throwing assertions for accessDenied, failed, and
opened while exercising the failed-to-sink path rather than the early return.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 712cb7ae-0df7-469e-9268-ab44ab5bf057

📥 Commits

Reviewing files that changed from the base of the PR and between 8f9a826 and fc1911c.

📒 Files selected for processing (3)
  • packages/workspace-client/src/lib/relaySocket/outageReporter.test.ts
  • packages/workspace-client/src/lib/relaySocket/outageReporter.ts
  • packages/workspace-client/src/lib/relaySocket/relaySocket.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/workspace-client/src/lib/relaySocket/relaySocket.ts
  • packages/workspace-client/src/lib/relaySocket/outageReporter.ts

@Kitenite
Kitenite merged commit 3c9288e into main Jul 13, 2026
19 checks passed
@Kitenite
Kitenite deleted the remote-workspace-disconne branch July 13, 2026 06:18

This branch was previously deployed

1 inactive deployment
preview — fc1911c1 Deployed Jul 13, 2026 by Kitenite via Deploy Web #6442
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