{"record":{"id":"e5bdc756c762729e","repo":"different-ai/openwork","slug":"electron-desktop-helper-is-unavailable-prop","errorCode":null,"errorMessage":"Electron desktop helper is unavailable: ${prop}","messagePattern":"Electron desktop helper is unavailable: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/app/src/app/lib/desktop.ts","lineNumber":282,"sourceCode":"// The cast is inherent to the Proxy pattern: the target is an empty cache and\n// members are fabricated on access. The contract typing above is what keeps\n// it honest (command names + signatures are checked on both sides).\nexport const desktopBridge = new Proxy(electronBridge, {\n  get(target, prop) {\n    if (typeof prop !== \"string\") return undefined;\n\n    // resolveWorkspaceListSelectedId is a pure function, not an IPC call\n    if (prop === \"resolveWorkspaceListSelectedId\") {\n      return resolveWorkspaceListSelectedId;\n    }\n\n    const cached = target[prop];\n    if (cached) return cached;\n\n    const fn = async (...args: unknown[]) => {\n      const invokeDesktop = window.__OPENWORK_ELECTRON__?.invokeDesktop;\n      if (!invokeDesktop) {\n        throw new Error(`Electron desktop helper is unavailable: ${prop}`);\n      }\n      // The Proxy is the one dynamic point in the bridge: `prop` is whatever\n      // property was accessed, already constrained by the DesktopBridge\n      // surface this Proxy is exported as.\n      return invokeDesktop(\n        prop as DesktopCommandName,\n        ...(args as DesktopCommandArgs<DesktopCommandName>),\n      );\n    };\n    target[prop] = fn;\n    return fn;\n  },\n}) as unknown as DesktopBridge;\n\n// ---------------------------------------------------------------------------\n// desktopFetch — proxies non-loopback requests through the Electron main\n// process. Loopback hosts (the local opencode/openwork server) use the\n// renderer's own fetch, which works against same-machine services. Cross-origin","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/app/src/app/lib/desktop.ts#L264-L300","documentation":"This is the dynamic twin of the typed helper: the DesktopBridge Proxy intercepts every property access and returns an async `fn` that forwards `prop` to `window.__OPENWORK_ELECTRON__.invokeDesktop`. If the Electron bridge is missing when the proxied method is called, it throws the same fail-fast error, embedding the accessed property name so you can see which desktop capability was attempted.","triggerScenarios":"Calling any method on the exported desktop bridge Proxy (a DesktopCommandName property not already cached) while `window.__OPENWORK_ELECTRON__` is undefined — web build, SSR/prerender, preload not loaded, or an Electron version whose preload lacks invokeDesktop.","commonSituations":"The same app bundle served in a browser where a desktop-only code path runs (e.g. auto-start on boot, shell integration); Next.js SSR executing a component that touches the bridge during server render; an Electron upgrade where the preload API was renamed or the preload failed silently; vitest/jsdom runs without a bridge mock.","solutions":["Run the code inside the Electron renderer with the preload loaded, so window.__OPENWORK_ELECTRON__.invokeDesktop is defined.","Wrap proxied bridge calls in an existence check (`window.__OPENWORK_ELECTRON__?.invokeDesktop`) and branch to a web fallback or hide desktop-only UI.","Mock the bridge in tests: `window.__OPENWORK_ELECTRON__ = { invokeDesktop: vi.fn() }` before importing modules that touch the Proxy.","Audit which property name appears in the message — a misspelled/removed command will also surface here if preload exposes invokeDesktop but rejects unknown commands; sync DesktopCommandName with preload definitions."],"exampleFix":"// before\nawait bridge.downloadFile(url);\n\n// after\nif (!window.__OPENWORK_ELECTRON__?.invokeDesktop) {\n  throw new Error(\"Desktop build required for this action\");\n}\nawait bridge.downloadFile(url);","handlingStrategy":"type-guard","validationCode":"if (typeof window === \"undefined\" || !window.__OPENWORK_ELECTRON__?.invokeDesktop) {\n  disableDesktopActions();\n}","typeGuard":"function isDesktopBridgeAvailable(): boolean {\n  return typeof window !== \"undefined\" &&\n    typeof window.__OPENWORK_ELECTRON__?.invokeDesktop === \"function\";\n}","tryCatchPattern":"try {\n  await bridge.someCommand(arg);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Electron desktop helper is unavailable\")) {\n    return fallbackForBrowser();\n  }\n  throw err;\n}","preventionTips":["Access the Proxy only after a runtime capability check.","Hide desktop-only menu items when the bridge is absent so they cannot be invoked in web builds.","Add a vitest setup file that stubs the bridge for every test environment.","Sync the DesktopBridge type surface with the preload API on every Electron upgrade."],"tags":["electron","proxy","missing-bridge","runtime-environment"],"backgroundTag":"electron-preload-bridge-unavailable","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}