different-ai/openwork · error
Electron desktop helper is unavailable: ${prop}
Error message
Electron desktop helper is unavailable: ${prop} What it means
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.
Source
Thrown at apps/app/src/app/lib/desktop.ts:282
// The cast is inherent to the Proxy pattern: the target is an empty cache and
// members are fabricated on access. The contract typing above is what keeps
// it honest (command names + signatures are checked on both sides).
export const desktopBridge = new Proxy(electronBridge, {
get(target, prop) {
if (typeof prop !== "string") return undefined;
// resolveWorkspaceListSelectedId is a pure function, not an IPC call
if (prop === "resolveWorkspaceListSelectedId") {
return resolveWorkspaceListSelectedId;
}
const cached = target[prop];
if (cached) return cached;
const fn = async (...args: unknown[]) => {
const invokeDesktop = window.__OPENWORK_ELECTRON__?.invokeDesktop;
if (!invokeDesktop) {
throw new Error(`Electron desktop helper is unavailable: ${prop}`);
}
// The Proxy is the one dynamic point in the bridge: `prop` is whatever
// property was accessed, already constrained by the DesktopBridge
// surface this Proxy is exported as.
return invokeDesktop(
prop as DesktopCommandName,
...(args as DesktopCommandArgs<DesktopCommandName>),
);
};
target[prop] = fn;
return fn;
},
}) as unknown as DesktopBridge;
// ---------------------------------------------------------------------------
// desktopFetch — proxies non-loopback requests through the Electron main
// process. Loopback hosts (the local opencode/openwork server) use the
// renderer's own fetch, which works against same-machine services. Cross-originView on GitHub (pinned to 2b7df46e8a)
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.
Example fix
// before
await bridge.downloadFile(url);
// after
if (!window.__OPENWORK_ELECTRON__?.invokeDesktop) {
throw new Error("Desktop build required for this action");
}
await bridge.downloadFile(url); Defensive patterns
Strategy: type-guard
Validate before calling
if (typeof window === "undefined" || !window.__OPENWORK_ELECTRON__?.invokeDesktop) {
disableDesktopActions();
} Type guard
function isDesktopBridgeAvailable(): boolean {
return typeof window !== "undefined" &&
typeof window.__OPENWORK_ELECTRON__?.invokeDesktop === "function";
} Try / catch
try {
await bridge.someCommand(arg);
} catch (err) {
if (err instanceof Error && err.message.startsWith("Electron desktop helper is unavailable")) {
return fallbackForBrowser();
}
throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Electron desktop helper is unavailable: ${command}
- Failed to open browser
- ${result}
- Electron eval relaunch helper is unavailable.
- latest-mac.yml is missing version.
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/e5bdc756c762729e.
Report an issue: GitHub.