feat(desktop): surface local server startup failures (#30822)

This commit is contained in:
Luke Parker 2026-06-05 08:45:48 +10:00 committed by GitHub
commit b1a7ee5695
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 175 additions and 5 deletions

View file

@ -21,6 +21,7 @@ import { createEffect, createResource, onCleanup, onMount, Show } from "solid-js
import { render } from "solid-js/web"
import pkg from "../../package.json"
import { initI18n, t } from "./i18n"
import { initializationData, initializationReady } from "./initialization"
import { resetZoom, setPinchZoomEnabled, webviewZoom, zoomIn, zoomOut } from "./webview-zoom"
import "./styles.css"
import { useTheme } from "@opencode-ai/ui/theme"
@ -329,7 +330,7 @@ render(() => {
const [locale] = createResource(loadLocale)
const servers = () => {
const data = sidecar()
const data = initializationData(sidecar)
if (!data) return []
const server: ServerConnection.Sidecar = {
displayName: "Local Server",
@ -383,7 +384,7 @@ render(() => {
<Show
when={
!defaultServer.loading &&
!sidecar.loading &&
initializationReady(sidecar) &&
!windowConfig.loading &&
!windowCount.loading &&
!locale.loading

View file

@ -0,0 +1,73 @@
import { describe, expect, test } from "bun:test"
import { initializationData, initializationReady } from "./initialization"
describe("desktop renderer initialization", () => {
test("throws the original initialization error before rendering server providers", () => {
const error = new Error("sidecar startup failed")
try {
initializationData(Object.assign(() => undefined, { error }))
throw new Error("expected initialization to fail")
} catch (failure) {
expect(failure).toBe(error)
expect((failure as Error & { localServerStartup?: boolean }).localServerStartup).toBe(true)
}
})
test("removes Electron's remote invocation wrapper from startup errors", () => {
const error = new Error(
"Error invoking remote method 'await-initialization': Error: Cannot migrate session_message projections",
)
try {
initializationData(Object.assign(() => undefined, { error }))
throw new Error("expected initialization to fail")
} catch (failure) {
expect(failure).toBe(error)
expect((failure as Error).message).toBe("Cannot migrate session_message projections")
}
})
test("returns initialized sidecar data", () => {
const sidecar = { url: "http://127.0.0.1:1234", username: "opencode", password: "secret" }
expect(initializationData(Object.assign(() => sidecar, { error: undefined }))).toBe(sidecar)
})
test("does not discard falsy initialization errors", () => {
let caught: unknown
try {
initializationData(Object.assign(() => undefined, { error: "" }))
} catch (error) {
caught = error
}
expect(caught).toBeInstanceOf(Error)
if (!(caught instanceof Error)) return
expect(caught.message).toBe("")
expect((caught as Error & { localServerStartup?: boolean }).localServerStartup).toBe(true)
})
test("checks initialization errors before rendering server providers", () => {
const error = new Error("sidecar startup failed")
expect(() => initializationReady(Object.assign(() => undefined, { error, loading: false }))).toThrow(error)
})
test("waits for pending initialization without reading it", () => {
let reads = 0
expect(
initializationReady(
Object.assign(
() => {
reads++
return undefined
},
{ error: undefined, loading: true },
),
),
).toBe(false)
expect(reads).toBe(0)
})
})

View file

@ -0,0 +1,22 @@
export function initializationData<A>(state: (() => A | undefined) & { error: unknown }) {
if (state.error !== undefined) throw markLocalServerStartup(state.error)
return state()
}
function markLocalServerStartup(error: unknown) {
const failure = error instanceof Error ? error : new Error(String(error))
const prefix = "Error invoking remote method 'await-initialization': Error: "
if (failure.message.startsWith(prefix)) {
const previous = failure.message
failure.message = failure.message.slice(prefix.length)
if (failure.stack) failure.stack = failure.stack.replace(`Error: ${previous}`, `Error: ${failure.message}`)
}
Object.defineProperty(failure, "localServerStartup", { value: true })
return failure
}
export function initializationReady<A>(state: (() => A | undefined) & { error: unknown; loading: boolean }) {
if (state.loading) return false
initializationData(state)
return true
}