summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorShoubhit Dash <[email protected]>2026-04-22 16:35:13 +0530
committerAiden Cline <[email protected]>2026-04-23 00:25:38 -0400
commit6196b81e0afb33cdcfb4b44e82f18e857d6c4dc5 (patch)
treedfc5049eaa2b86fbc45d203774f68f7ca7302481 /packages
parentd884ab73d5516d301a740b2bdea174f6b485d6dc (diff)
downloadopencode-6196b81e0afb33cdcfb4b44e82f18e857d6c4dc5.tar.gz
opencode-6196b81e0afb33cdcfb4b44e82f18e857d6c4dc5.zip
fix(tui): fail fast on invalid session startup (#23837)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/cli/cmd/tui/attach.ts16
-rw-r--r--packages/opencode/src/cli/cmd/tui/routes/session/index.tsx55
-rw-r--r--packages/opencode/src/cli/cmd/tui/thread.ts14
-rw-r--r--packages/opencode/src/cli/cmd/tui/validate-session.ts24
-rw-r--r--packages/opencode/src/util/error.ts9
5 files changed, 97 insertions, 21 deletions
diff --git a/packages/opencode/src/cli/cmd/tui/attach.ts b/packages/opencode/src/cli/cmd/tui/attach.ts
index 9a93f3f57..cb6b95a56 100644
--- a/packages/opencode/src/cli/cmd/tui/attach.ts
+++ b/packages/opencode/src/cli/cmd/tui/attach.ts
@@ -3,6 +3,8 @@ import { UI } from "@/cli/ui"
import { tui } from "./app"
import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
+import { errorMessage } from "@/util/error"
+import { validateSession } from "./validate-session"
export const AttachCommand = cmd({
command: "attach <url>",
@@ -65,6 +67,20 @@ export const AttachCommand = cmd({
return { Authorization: auth }
})()
const config = await TuiConfig.get()
+
+ try {
+ await validateSession({
+ url: args.url,
+ sessionID: args.session,
+ directory,
+ headers,
+ })
+ } catch (error) {
+ UI.error(errorMessage(error))
+ process.exitCode = 1
+ return
+ }
+
await tui({
url: args.url,
config,
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
index 06be5dfbe..2f5da1d23 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
@@ -68,6 +68,7 @@ import { Flag } from "@/flag/flag"
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
import parsers from "../../../../../../parsers-config.ts"
import * as Clipboard from "../../util/clipboard"
+import { errorMessage } from "@/util/error"
import { Toast, useToast } from "../../ui/toast"
import { useKV } from "../../context/kv.tsx"
import * as Editor from "../../util/editor"
@@ -180,31 +181,43 @@ export function Session() {
const toast = useToast()
const sdk = useSDK()
- createEffect(async () => {
- const previousWorkspace = project.workspace.current()
- const result = await sdk.client.session.get({ sessionID: route.sessionID }, { throwOnError: true })
- if (!result.data) {
+ createEffect(() => {
+ const sessionID = route.sessionID
+ void (async () => {
+ const previousWorkspace = project.workspace.current()
+ const result = await sdk.client.session.get({ sessionID }, { throwOnError: true })
+ if (!result.data) {
+ toast.show({
+ message: `Session not found: ${sessionID}`,
+ variant: "error",
+ duration: 5000,
+ })
+ navigate({ type: "home" })
+ return
+ }
+
+ if (result.data.workspaceID !== previousWorkspace) {
+ project.workspace.set(result.data.workspaceID)
+
+ // Sync all the data for this workspace. Note that this
+ // workspace may not exist anymore which is why this is not
+ // fatal. If it doesn't we still want to show the session
+ // (which will be non-interactive)
+ try {
+ await sync.bootstrap({ fatal: false })
+ } catch {}
+ }
+ await sync.session.sync(sessionID)
+ if (route.sessionID === sessionID && scroll) scroll.scrollBy(100_000)
+ })().catch((error) => {
+ if (route.sessionID !== sessionID) return
toast.show({
- message: `Session not found: ${route.sessionID}`,
+ message: errorMessage(error),
variant: "error",
+ duration: 5000,
})
navigate({ type: "home" })
- return
- }
-
- if (result.data.workspaceID !== previousWorkspace) {
- project.workspace.set(result.data.workspaceID)
-
- // Sync all the data for this workspace. Note that this
- // workspace may not exist anymore which is why this is not
- // fatal. If it doesn't we still want to show the session
- // (which will be non-interactive)
- try {
- await sync.bootstrap({ fatal: false })
- } catch (e) {}
- }
- await sync.session.sync(route.sessionID)
- if (scroll) scroll.scrollBy(100_000)
+ })
})
let lastSwitch: string | undefined = undefined
diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts
index e3e9eb811..a2a53ecaf 100644
--- a/packages/opencode/src/cli/cmd/tui/thread.ts
+++ b/packages/opencode/src/cli/cmd/tui/thread.ts
@@ -16,6 +16,7 @@ import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
import { writeHeapSnapshot } from "v8"
import { TuiConfig } from "./config/tui"
import { OPENCODE_PROCESS_ROLE, OPENCODE_RUN_ID, ensureRunID, sanitizedProcessEnv } from "@/util/opencode-process"
+import { validateSession } from "./validate-session"
declare global {
const OPENCODE_WORKER_PATH: string
@@ -202,6 +203,19 @@ export const TuiThreadCommand = cmd({
events: createEventSource(client),
}
+ try {
+ await validateSession({
+ url: transport.url,
+ sessionID: args.session,
+ directory: cwd,
+ fetch: transport.fetch,
+ })
+ } catch (error) {
+ UI.error(errorMessage(error))
+ process.exitCode = 1
+ return
+ }
+
setTimeout(() => {
client.call("checkUpgrade", { directory: cwd }).catch(() => {})
}, 1000).unref?.()
diff --git a/packages/opencode/src/cli/cmd/tui/validate-session.ts b/packages/opencode/src/cli/cmd/tui/validate-session.ts
new file mode 100644
index 000000000..e2a21d51e
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/validate-session.ts
@@ -0,0 +1,24 @@
+import { createOpencodeClient } from "@opencode-ai/sdk/v2"
+import { SessionID } from "@/session/schema"
+
+export async function validateSession(input: {
+ url: string
+ sessionID?: string
+ directory?: string
+ fetch?: typeof fetch
+ headers?: RequestInit["headers"]
+}) {
+ if (!input.sessionID) return
+
+ const result = SessionID.zod.safeParse(input.sessionID)
+ if (!result.success) {
+ throw new Error(`Invalid session ID: ${result.error.issues.at(0)?.message ?? "unknown error"}`)
+ }
+
+ await createOpencodeClient({
+ baseUrl: input.url,
+ directory: input.directory,
+ fetch: input.fetch,
+ headers: input.headers,
+ }).session.get({ sessionID: result.data }, { throwOnError: true })
+}
diff --git a/packages/opencode/src/util/error.ts b/packages/opencode/src/util/error.ts
index 75fef9fc9..76cb9c7cf 100644
--- a/packages/opencode/src/util/error.ts
+++ b/packages/opencode/src/util/error.ts
@@ -26,6 +26,15 @@ export function errorMessage(error: unknown): string {
return error.message
}
+ if (
+ isRecord(error) &&
+ isRecord(error.data) &&
+ typeof error.data.message === "string" &&
+ error.data.message
+ ) {
+ return error.data.message
+ }
+
const text = String(error)
if (text && text !== "[object Object]") return text