Skip to content

fix(desktop): time out daemon requests and show connection errors - #233

Merged
ralyodio merged 2 commits into
masterfrom
fix/desktop-daemon-client-timeout
Sep 25, 2026
Merged

ralyodio merged 2 commits into
masterfrom
fix/desktop-daemon-client-timeout

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

What

The desktop app's IPC client (apps/desktop/src/main/daemon-client.ts) now behaves like the CLI client after #229:

  • Each request has a 10s timeout. The timer is cleared when the reply arrives, when the write fails, and when the socket closes. A request that never gets an answer rejects with threatcrushd did not answer "<method>" within 10s; if it is older than this app, update it.
  • When the socket closes, every pending request is rejected (daemon connection closed).
  • A daemon unknown method: … error becomes threatcrushd is older than this app (unknown method: …); update it. Other daemon errors pass through unchanged.

The client's errors now reach the UI:

  • Settings → Daemon Connection: the CONNECT button used to be a 2s setTimeout simulation that never contacted the daemon. It now calls the real connect-daemon handler and shows the result: Connected to threatcrushd vX at <socket> or Not connected: <error>. The Host/Port fields are gone because the main process ignores them. It only ever connects to the local unix socket.
  • connect-daemon handshake: if the status request fails (for example, it times out), the handler used to return connected: true anyway. It now closes the client and returns connected: false with the error. subscribe is still best-effort.

CI: added a Test desktop app step to PR checks so the new suite runs.

Why

An older daemon (before #229) never replies to a method it doesn't know. The desktop client had no timeout, so any request to such a daemon hung forever.

Why duplicate instead of share

apps/desktop doesn't depend on the CLI. The CLI's IpcClient imports the daemon's paths, control-token and protocol modules and has a typed per-method API. The desktop client is a generic request(method) with push listeners. Sharing would mean pulling CLI daemon modules into the Electron main bundle, or creating a new shared package, just for about 20 lines. So the logic is duplicated, with a comment pointing at apps/cli/src/core/ipc-client.ts.

Verification

  • apps/desktop/src/main/__tests__/daemon-client.test.ts is a new vitest suite (node env, fake unix socket server in a temp dir), 5 tests. It covers: a never-answered request times out, unknown method maps to the update message, other errors pass through, a pending request rejects on close, and an answered request leaves no timer behind.
    • Before the fix: 2 failures. The timeout test hit vitest's 5s test timeout (the request never settles), and the unknown-method test got the raw unknown method: top_sources.
    • After: 5/5 pass.
  • pnpm --filter @profullstack/threatcrush-desktop build passes.
  • pnpm --filter @profullstack/threatcrush test: 28 files, 258 tests pass.
  • Smoke test of the built out/main/index.js: Electron itself can't launch on the dev host (no GTK libs). The bundle was loaded with only the electron module stubbed. Its real connect-daemon handler was bridged to the real Settings page rendered in jsdom, against a fake daemon.
    • Daemon never answers status: the button shows CONNECTING..., then after 10.1s shows CONNECT and Not connected: threatcrushd did not answer "status" within 10s; if it is older than this app, update it.
    • Daemon answers unknown method: status: Not connected: threatcrushd is older than this app (unknown method: status); update it.
    • Healthy daemon: CONNECTED, Connected to threatcrushd v9.9.9 at ….
    • Pre-fix build, same silent daemon: the connect-daemon handler was still pending when curl gave up at 30s.

The desktop IPC client waited forever for a reply, so a daemon that never
answers (e.g. one older than #229 asked for a method it does not know) hung
the app. Mirror the CLI client from #229: a 10s per-request timeout cleared on
reply, write error and close; pending requests rejected on close; and an
'unknown method' error mapped to an update-the-daemon message.

The Settings page's Connect button was a simulation; it now calls the real
connect-daemon handler and shows the daemon's error. A handshake whose status
request fails now reports not connected with the error instead of claiming a
connection.
@ralyodio
ralyodio force-pushed the fix/desktop-daemon-client-timeout branch from e6344e8 to 7854a5e Compare September 25, 2026 16:56
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

12 finding(s)

HIGH/CRITICAL: 1 | MEDIUM: 6 | LOW: 5

Severity Rule Location
HIGH secret-aws-access-key prd/0003-detect-hardcoded-secrets-before-they-are-committed-or-served.md:126
MEDIUM js-open-redirect apps/web/src/app/auth/login/page.tsx:50
MEDIUM js-unescaped-html-sink apps/web/src/app/hire/page.tsx:104
MEDIUM js-unescaped-html-sink apps/web/src/app/hire/page.tsx:108
MEDIUM js-open-redirect apps/web/src/components/funding/FundingClient.tsx:97
MEDIUM js-unescaped-html-sink apps/web/src/components/GuideReader.tsx:265
MEDIUM js-uninitialized-buffer packages/scan/src/node-rules.ts:456
LOW secret-generic-credential PRD.md:269
LOW tls-verification-disabled prd/0004-find-dangerous-code-patterns-without-pretending-to-be-a-compiler.md:121
LOW tls-verification-disabled prd/0004-find-dangerous-code-patterns-without-pretending-to-be-a-compiler.md:122
LOW sh-remote-script-execution scripts/smoke-test.sh:47
LOW secret-aws-access-key scripts/smoke-test.sh:112

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 6fb2eca into master Sep 25, 2026
11 checks passed
@ralyodio
ralyodio deleted the fix/desktop-daemon-client-timeout branch September 25, 2026 17:05
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