Skip to content

fix(desktop): resolve app icon from asar.unpacked so the app can launch - #423

Merged
Kyzcreig merged 1 commit into
mainfrom
fix/desktop-app-icon-asar
Jul 25, 2026
Merged

Kyzcreig merged 1 commit into
mainfrom
fix/desktop-app-icon-asar

Conversation

@Kyzcreig

Copy link
Copy Markdown
Collaborator

The symptom

The packaged macOS app died at startup with an uncaught main-process exception and never opened a window:

A JavaScript error occurred in the main process, Uncaught Exception:
Error: Failed to load image from path
  '/Applications/Hermes.app/Contents/Resources/app.asar/public/apple-touch-icon.png'
  at createWindow (.../electron-main.mjs)

Found during the 2026-07-23 parity deploy by taking a screenshot of the freshly-installed app. Every other signal was green — install-stamp SHA matched the pin, 730 desktop tests passed, all build gates OK — because the crash only happens in a packaged build.

Root cause

getAppIconPath() returns the first APP_ICON_PATHS candidate that fs.statSync() finds. There is a real asymmetry between the two readers:

  • fs.statSync() can read paths inside app.asar (Electron shims fs), so the in-asar public/ candidate passes the existence check.
  • app.dock.setIcon() is a native call that cannot read inside an asar, so the same path throws.

The in-asar candidate was ordered first, got selected, and then threw — aborting createWindow() and leaving the app with no window at all.

The fix

  1. Order the asar.unpacked candidate first. dist/** is in build.asarUnpack, so it resolves to a real on-disk file in a packaged app. The in-asar paths remain as fallbacks for dev runs, where APP_ROOT has no .asar segment.
  2. Wrap setIcon() in try/catch. A cosmetic dock icon must never be able to take down the window — defense in depth for any future unreadable-path case.

Tests

New electron/app-icon-path.test.ts locks three contracts:

  • the unpackedPathFor() candidate is ordered first,
  • the setIcon() call is guarded,
  • build.asarUnpack still unpacks dist/ (the precondition the unpacked candidate depends on).

RED-proven: reverting the ordering makes the first test fail with the explanatory message; restoring it passes. 3/3 green.

Verification

Rebuilt and installed on the Mac Studio at this SHA, launched the app, and confirmed a working window: session list, chat transcript, channel counts, "Gateway ready". No error dialog.

The packaged macOS app died at startup with an uncaught main-process
exception and never opened a window:

    A JavaScript error occurred in the main process, Uncaught Exception:
    Error: Failed to load image from path
      '/Applications/Hermes.app/Contents/Resources/app.asar/public/apple-touch-icon.png'
      at createWindow (.../electron-main.mjs)

getAppIconPath() returns the first APP_ICON_PATHS candidate that
fs.statSync() finds. Electron shims fs so it CAN read inside app.asar,
but app.dock.setIcon() is a native call that CANNOT — so the in-asar
'public/' candidate was selected and then threw, aborting createWindow().

Order the asar.unpacked candidate first (dist/** is in build.asarUnpack,
so it resolves to a real on-disk file in a packaged app) and keep the
in-asar paths as dev-run fallbacks, where APP_ROOT has no .asar segment.

Also wrap the setIcon() call in try/catch: a cosmetic dock icon must
never be able to take down the window.

Adds electron/app-icon-path.test.ts locking both contracts plus the
build.asarUnpack precondition the unpacked candidate depends on.
RED-proven: the ordering test fails against the previous ordering.
@Kyzcreig

Copy link
Copy Markdown
Collaborator Author

FleetReview

Confidence: 3/5

Findings

  • P1 apps/desktop/electron/app-icon-path.test.ts:99 — Incomplete Check

FleetReview provenance · models: B=gpt-5.6-sol, C=claude-code-opus-4-8, F=gpt-5.6-sol, G=grok-4.5 · cost: $2.40 · duration: 10m 01s · rounds: 1 · files examined: 2

@Kyzcreig
Kyzcreig merged commit 6415520 into main Jul 25, 2026
34 checks passed
@Kyzcreig
Kyzcreig deleted the fix/desktop-app-icon-asar branch July 25, 2026 14:48
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