fix(preload): an IPC failure's code never reaches the window, so the unsupported cookie file message never shows#1949

opened6d agobyMMatt Donovan

Note

Written by claude-opus-5-5 via Claude Code on behalf of Matt.

What happened. When the main process refuses a request with a named reason, the window never learns which reason it was. The preload script puts the reason's code on the Error it rejects with, but Electron copies a rejected Error across the context bridge as a plain error with only its message and drops every other property. Every renderer check on an error's code is therefore dead in the running app. The one on main today is the Personas screen: importing a cookie file in a format Birdbrain cannot read should show the inline "unsupported file" message under the persona, and it never does. The window shows only the generic failure toast.

Steps to reproduce.

  1. Open Settings, then Personas, and create a persona.
  2. Import a cookie file in an unsupported format.
  3. No inline message appears under the persona. The generic failure toast shows instead.

What you expected. The inline message naming the unsupported file, as the Personas screen's own test asserts with a fake bridge that runs in the same JavaScript world.

Why this was kept out of the change that found it. The review of pull request 1948 (issue 811) found it, because that change tried to show a taken tag name as an info notice by reading the same code. Carrying codes across the bridge changes how every channel rejects, so it needs its own design; pull request 1948 drops its notice instead.

Where it is, the repro, and the options

Where: src/preload/index.ts:22-25 (unwrapIpc sets .code on the Error); readers at src/renderer/components/settings/PersonasSection.tsx:276-280 (PERSONA_COOKIE_FILE_UNSUPPORTED, thrown at src/main/ipcHandlers.ts:602).

Repro: a bare Electron window with sandbox and contextIsolation on, a preload that mirrors unwrapIpc, and a handler returning { ok: false, error, code: 'TAG_NAME_TAKEN' }. Run with the repository's Electron 44.8.0 under xvfb-run -a, the main world sees {"isError":true,"message":"A tag named ‘dup’ already exists","code":"undefined","ownKeys":["stack","message"]}. Electron's context isolation docs say custom properties on an Error are lost; upstream electron/electron#25596 was closed as not planned.

Options: (a) the preload rejects with a plain object that keeps message and code, and a small renderer helper turns it back into an Error, so instanceof Error checks keep working; (b) the code travels inside the message in a fixed prefix the renderer strips; (c) the bridge resolves the envelope and each caller unwraps it. Recommended: (a), since plain objects keep their properties across the bridge and the change stays in the preload plus one helper.

Birdbrain version: the current main branch. Operating system: Linux (the bridge behaviour is Electron's, on every platform).

Synced from GitHub

The fix this issue recommends (option a) is now on main for the sharing screens, merged in PR #1965.

The Personas cookie-file message still uses the old path and still never shows in the built app, so this issue stays open for the remaining channels.

Where the pattern lives

PR #1965's commit "keep refusal codes across the context bridge" makes the preload resolve sharing refusals as a plain object with message and code, and the renderer rebuilds the Error. Its reviewer confirmed in Electron 44.9.1 that the plain object keeps code across the bridge and a rejected Error does not. The reader at src/renderer/components/settings/PersonasSection.tsx:276-279 still depends on .code on a rejected Error.