summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOpeOginni <[email protected]>2026-02-24 15:48:59 +0100
committerGitHub <[email protected]>2026-02-24 14:48:59 +0000
commitcc02476ea5e02d3c827006dcd0c830f7673556e5 (patch)
treefe5a89e39d66351ee00f20df86fe6dffbae40cef
parent5190589632c97b570bb6f9035aa5c80c0fe833e7 (diff)
downloadopencode-cc02476ea5e02d3c827006dcd0c830f7673556e5.tar.gz
opencode-cc02476ea5e02d3c827006dcd0c830f7673556e5.zip
refactor: replace error handling with serverErrorMessage utility and checks for if error is ConfigInvalidError (#14685)
-rw-r--r--packages/app/src/context/global-sync.tsx9
-rw-r--r--packages/app/src/context/global-sync/bootstrap.ts8
-rw-r--r--packages/app/src/utils/server-errors.test.ts69
-rw-r--r--packages/app/src/utils/server-errors.ts32
4 files changed, 110 insertions, 8 deletions
diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx
index 7e242130f..9fbc93bde 100644
--- a/packages/app/src/context/global-sync.tsx
+++ b/packages/app/src/context/global-sync.tsx
@@ -36,6 +36,7 @@ import type { ProjectMeta } from "./global-sync/types"
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
import { sanitizeProject } from "./global-sync/utils"
import { usePlatform } from "./platform"
+import { formatServerError } from "@/utils/server-errors"
type GlobalStore = {
ready: boolean
@@ -51,11 +52,6 @@ type GlobalStore = {
reload: undefined | "pending" | "complete"
}
-function errorMessage(error: unknown) {
- if (error instanceof Error && error.message) return error.message
- if (typeof error === "string" && error) return error
- return "Unknown error"
-}
function createGlobalSync() {
const globalSDK = useGlobalSDK()
@@ -207,8 +203,9 @@ function createGlobalSync() {
console.error("Failed to load sessions", err)
const project = getFilename(directory)
showToast({
+ variant: "error",
title: language.t("toast.session.listFailed.title", { project }),
- description: errorMessage(err),
+ description: formatServerError(err),
})
})
diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts
index 6e7714828..b35f1cd80 100644
--- a/packages/app/src/context/global-sync/bootstrap.ts
+++ b/packages/app/src/context/global-sync/bootstrap.ts
@@ -16,6 +16,7 @@ import { batch } from "solid-js"
import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
import type { State, VcsCache } from "./types"
import { cmp, normalizeProviderList } from "./utils"
+import { formatServerError } from "@/utils/server-errors"
type GlobalStore = {
ready: boolean
@@ -133,8 +134,11 @@ export async function bootstrapDirectory(input: {
} catch (err) {
console.error("Failed to bootstrap instance", err)
const project = getFilename(input.directory)
- const message = err instanceof Error ? err.message : String(err)
- showToast({ title: `Failed to reload ${project}`, description: message })
+ showToast({
+ variant: "error",
+ title: `Failed to reload ${project}`,
+ description: formatServerError(err)
+ })
input.setStore("status", "partial")
return
}
diff --git a/packages/app/src/utils/server-errors.test.ts b/packages/app/src/utils/server-errors.test.ts
new file mode 100644
index 000000000..1969d1afc
--- /dev/null
+++ b/packages/app/src/utils/server-errors.test.ts
@@ -0,0 +1,69 @@
+import { describe, expect, test } from "bun:test"
+import type { ConfigInvalidError } from "./server-errors"
+import { formatServerError, parseReabaleConfigInvalidError } from "./server-errors"
+
+describe("parseReabaleConfigInvalidError", () => {
+ test("formats issues with file path", () => {
+ const error = {
+ name: "ConfigInvalidError",
+ data: {
+ path: "opencode.config.ts",
+ issues: [
+ { path: ["settings", "host"], message: "Required" },
+ { path: ["mode"], message: "Invalid" },
+ ],
+ },
+ } satisfies ConfigInvalidError
+
+ const result = parseReabaleConfigInvalidError(error)
+
+ expect(result).toBe(
+ ["Invalid configuration", "opencode.config.ts", "settings.host: Required", "mode: Invalid"].join("\n"),
+ )
+ })
+
+ test("uses trimmed message when issues are missing", () => {
+ const error = {
+ name: "ConfigInvalidError",
+ data: {
+ path: "config",
+ message: " Bad value ",
+ },
+ } satisfies ConfigInvalidError
+
+ const result = parseReabaleConfigInvalidError(error)
+
+ expect(result).toBe(["Invalid configuration", "Bad value"].join("\n"))
+ })
+})
+
+describe("formatServerError", () => {
+ test("formats config invalid errors", () => {
+ const error = {
+ name: "ConfigInvalidError",
+ data: {
+ message: "Missing host",
+ },
+ } satisfies ConfigInvalidError
+
+ const result = formatServerError(error)
+
+ expect(result).toBe(["Invalid configuration", "Missing host"].join("\n"))
+ })
+
+ test("returns error messages", () => {
+ expect(formatServerError(new Error("Request failed with status 503"))).toBe("Request failed with status 503")
+ })
+
+ test("returns provided string errors", () => {
+ expect(formatServerError("Failed to connect to server")).toBe("Failed to connect to server")
+ })
+
+ test("falls back to unknown", () => {
+ expect(formatServerError(0)).toBe("Unknown error")
+ })
+
+ test("falls back for unknown error objects and names", () => {
+ expect(formatServerError({ name: "ServerTimeoutError", data: { seconds: 30 } })).toBe("Unknown error")
+ })
+})
diff --git a/packages/app/src/utils/server-errors.ts b/packages/app/src/utils/server-errors.ts
new file mode 100644
index 000000000..4b9727e61
--- /dev/null
+++ b/packages/app/src/utils/server-errors.ts
@@ -0,0 +1,32 @@
+export type ConfigInvalidError = {
+ name: "ConfigInvalidError"
+ data: {
+ path?: string
+ message?: string
+ issues?: Array<{ message: string; path: string[] }>
+ }
+}
+
+export function formatServerError(error: unknown) {
+ if (isConfigInvalidErrorLike(error)) return parseReabaleConfigInvalidError(error)
+ if (error instanceof Error && error.message) return error.message
+ if (typeof error === "string" && error) return error
+ return "Unknown error"
+}
+
+function isConfigInvalidErrorLike(error: unknown): error is ConfigInvalidError {
+ if (typeof error !== "object" || error === null) return false
+ const o = error as Record<string, unknown>
+ return o.name === "ConfigInvalidError" && typeof o.data === "object" && o.data !== null
+}
+
+export function parseReabaleConfigInvalidError(errorInput: ConfigInvalidError) {
+ const head = "Invalid configuration"
+ const file = errorInput.data.path && errorInput.data.path !== "config" ? errorInput.data.path : ""
+ const detail = errorInput.data.message?.trim() ?? ""
+ const issues = (errorInput.data.issues ?? []).map((issue) => {
+ return `${issue.path.join(".")}: ${issue.message}`
+ })
+ if (issues.length) return [head, file, "", ...issues].filter(Boolean).join("\n")
+ return [head, file, detail].filter(Boolean).join("\n")
+}