Skip to content

fix(desktop): clean stale tsc emit + guard gateway WS URLs - #69453

Merged
ethernet8023 merged 2 commits into
mainfrom
bb/stale-js-shadow-guard
Jul 22, 2026
Merged

fix(desktop): clean stale tsc emit + guard gateway WS URLs#69453
ethernet8023 merged 2 commits into
mainfrom
bb/stale-js-shadow-guard

Conversation

@OutThisLife

@OutThisLife OutThisLife commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Symptom

Desktop app boot-loops on "Could not connect to Hermes gateway". Renderer console shows:

WebSocket connection to 'ws://127.0.0.1:5174/[object%20Object]' failed: WebSocket is closed before the connection is established.

Survives app restarts, machine reboots, and Vite cache wipes. Backend is fully healthy the whole time (/api/status ok, /api/ws?token= upgrades with 101).

Root cause

A stray tsc run emitted compiled .js files next to their .ts sources in apps/shared/src/ (index.js, json-rpc-gateway.js, websocket-url.js). .gitignore already covers these artifacts — which also makes them invisible in git status — and Vite resolves extensionless imports .js before .ts, so the renderer silently ran the stale compiled copies.

The Jul 16 artifact of websocket-url.js predated #68250's getGatewayWsUrl contract change (bare string → { ok, wsUrl }). Its old if (fresh) return fresh handed the whole IPC result object to new WebSocket(), which coerced it to [object Object] against the page origin. Boot never completed, so the main process respawned the backend in an endless loop (200+ cycles logged).

Fix

  1. tsc -b . --clean before vite in dev:renderer — same as existing prebuild. Deletes project-matched emit (sibling .js next to .ts) instead of hand-scanning the tree.
  2. JsonRpcGatewayClient.connect() — rejects non-ws:///wss:// URLs with a readable error instead of letting new WebSocket() coerce garbage. Future contract skew fails with gateway connect() requires a ws:// or wss:// URL string, got type "object" instead of an opaque dial to [object%20Object].

Tests

  • src/lib/json-rpc-gateway-url-guard.test.ts: object rejected; non-ws string rejected; state stays idle on rejection; ws:///wss:// accepted and open.

npx vitest run src/lib/json-rpc-gateway-url-guard.test.ts: 4 passed.

@OutThisLife
OutThisLife requested a review from a team July 22, 2026 15:28
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

running on 02fa447

CI timings

CI timings · View job

Wall time 9m20s vs 11m30s (-18.8%). 3 job(s) slower, 2 faster, 2 unchanged.

  • Build&Test Docker image / build (amd64, ubuntu-latest, linux/amd64, type=gha,scope=docker-amd64, type=gha,mode=max,scope=do...: -110.0s
  • Build&Test Docker image / build (arm64, ubuntu-24.04-arm, linux/arm64, type=gha,scope=docker-arm64, type=gha,mode=max,scope...: +105.0s
  • OSV scan / Scan lockfiles / osv-scan: +4.0s
  • Check uv.lock / uv lock --check: +3.0s
  • Detect affected areas: -2.0s

@OutThisLife OutThisLife changed the title fix(desktop): guard against compiled .js shadowing .ts renderer sources fix(desktop): clean stale tsc emit + guard gateway WS URLs Jul 22, 2026
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/desktop Electron desktop app (apps/desktop/*) sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 22, 2026
A stray tsc run can emit foo.js next to foo.ts under apps/shared/src or
apps/desktop/src. .gitignore hides the artifact from git status, but Vite
resolves extensionless imports .js-before-.ts, so the renderer silently runs
the stale compiled copy.

tsc -b . --clean already knows the emit graph and deletes
matching outputs. Run it before vite in all dev scripts.

This bit for real: a Jul 16 artifact of websocket-url.js predated the #68250
getGatewayWsUrl contract change ({ ok, wsUrl } IPC result), so its old
'if (fresh) return fresh' handed the whole result object to new WebSocket(),
dialing ws://127.0.0.1:5174/[object%20Object] on every boot. The desktop app
could never connect, and the failure survived reboots and cache wipes because
the poison lived in src/.

JsonRpcGatewayClient.connect() now rejects non-ws:// URLs with a readable
error instead of letting new WebSocket() coerce an object into
[object%20Object], so any future contract skew fails diagnosably.
@ethernet8023
ethernet8023 force-pushed the bb/stale-js-shadow-guard branch from dae2826 to 9160f10 Compare July 22, 2026 17:20
@ethernet8023
ethernet8023 enabled auto-merge (squash) July 22, 2026 18:32
@ethernet8023
ethernet8023 disabled auto-merge July 22, 2026 18:32
@ethernet8023
ethernet8023 enabled auto-merge July 22, 2026 18:32
@ethernet8023
ethernet8023 merged commit 5a40fd3 into main Jul 22, 2026
27 checks passed
@ethernet8023
ethernet8023 deleted the bb/stale-js-shadow-guard branch July 22, 2026 18:36
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…hadow-guard

fix(desktop): clean stale tsc emit + guard gateway WS URLs
33hodl pushed a commit to 33hodl/hermes-agent that referenced this pull request Aug 12, 2026
…hadow-guard

fix(desktop): clean stale tsc emit + guard gateway WS URLs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants