From 3806a406a1086d9d13189ffdbdacd306b66f26cd Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Thu, 29 Jan 2026 23:29:59 -0500 Subject: [PATCH 01/10] add --fork flag to duplicate sessions before continuing - Add --fork CLI flag for session forking with --continue or --session - Forked sessions get automatic (fork #N) title suffix via existing Session.fork() - Validate that --fork requires --continue or --session - Applies to both attach mode and bootstrap mode Fixes #11137 --- packages/opencode/src/cli/cmd/run.ts | 35 ++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 54248f96f3..791ff1672d 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -50,6 +50,10 @@ export const RunCommand = cmd({ describe: "session id to continue", type: "string", }) + .option("fork", { + describe: "fork the session before continuing (requires --continue or --session)", + type: "boolean", + }) .option("share", { type: "boolean", describe: "share the session", @@ -133,6 +137,11 @@ export const RunCommand = cmd({ process.exit(1) } + if (args.fork && !args.continue && !args.session) { + UI.error("--fork requires --continue or --session") + process.exit(1) + } + const execute = async (sdk: OpencodeClient, sessionID: string) => { const printEvent = (color: string, type: string, title: string) => { UI.println( @@ -279,11 +288,20 @@ export const RunCommand = cmd({ const sdk = createOpencodeClient({ baseUrl: args.attach }) const sessionID = await (async () => { + let id: string | undefined if (args.continue) { const result = await sdk.session.list() - return result.data?.find((s) => !s.parentID)?.id + id = result.data?.find((s) => !s.parentID)?.id + } else if (args.session) { + id = args.session } - if (args.session) return args.session + + if (id && args.fork) { + const forked = await sdk.session.fork({ sessionID: id }) + return forked.data?.id + } + + if (id) return id const title = args.title !== undefined @@ -354,11 +372,20 @@ export const RunCommand = cmd({ } const sessionID = await (async () => { + let id: string | undefined if (args.continue) { const result = await sdk.session.list() - return result.data?.find((s) => !s.parentID)?.id + id = result.data?.find((s) => !s.parentID)?.id + } else if (args.session) { + id = args.session } - if (args.session) return args.session + + if (id && args.fork) { + const forked = await sdk.session.fork({ sessionID: id }) + return forked.data?.id + } + + if (id) return id const title = args.title !== undefined From f9366af423baeb164310ffdd024fd50a75ba2e82 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Fri, 30 Jan 2026 01:01:35 -0500 Subject: [PATCH 02/10] rename --fork to --fork-session to match issue requirements --- packages/opencode/src/cli/cmd/run.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 791ff1672d..b1c7f2a3d2 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -50,7 +50,8 @@ export const RunCommand = cmd({ describe: "session id to continue", type: "string", }) - .option("fork", { + .option("fork-session", { + alias: ["fork"], describe: "fork the session before continuing (requires --continue or --session)", type: "boolean", }) @@ -137,8 +138,8 @@ export const RunCommand = cmd({ process.exit(1) } - if (args.fork && !args.continue && !args.session) { - UI.error("--fork requires --continue or --session") + if (args.forkSession && !args.continue && !args.session) { + UI.error("--fork-session requires --continue or --session") process.exit(1) } @@ -296,7 +297,7 @@ export const RunCommand = cmd({ id = args.session } - if (id && args.fork) { + if (id && args.forkSession) { const forked = await sdk.session.fork({ sessionID: id }) return forked.data?.id } @@ -380,7 +381,7 @@ export const RunCommand = cmd({ id = args.session } - if (id && args.fork) { + if (id && args.forkSession) { const forked = await sdk.session.fork({ sessionID: id }) return forked.data?.id } From c25420c72c3264fba39bf78521d2f9b9f8168248 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Fri, 30 Jan 2026 01:08:17 -0500 Subject: [PATCH 03/10] add --fork-session support to TUI command - Add fork option to Args interface - Add --fork-session flag to TUI command (thread.ts) - Add validation that --fork-session requires --continue or --session - Fork session before navigating when --fork-session is set --- packages/opencode/src/cli/cmd/tui/app.tsx | 26 +++++++++++++++---- .../opencode/src/cli/cmd/tui/context/args.tsx | 1 + packages/opencode/src/cli/cmd/tui/thread.ts | 11 ++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 10d7a25f88..128ce8dbf6 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -248,10 +248,18 @@ function App() { local.model.set({ providerID, modelID }, { recent: true }) } if (args.sessionID) { - route.navigate({ - type: "session", - sessionID: args.sessionID, - }) + if (args.fork) { + sdk.client.session.fork({ sessionID: args.sessionID }).then((result) => { + if (result.data?.id) { + route.navigate({ type: "session", sessionID: result.data.id }) + } + }) + } else { + route.navigate({ + type: "session", + sessionID: args.sessionID, + }) + } } }) }) @@ -265,7 +273,15 @@ function App() { .find((x) => x.parentID === undefined)?.id if (match) { continued = true - route.navigate({ type: "session", sessionID: match }) + if (args.fork) { + sdk.client.session.fork({ sessionID: match }).then((result) => { + if (result.data?.id) { + route.navigate({ type: "session", sessionID: result.data.id }) + } + }) + } else { + route.navigate({ type: "session", sessionID: match }) + } } }) diff --git a/packages/opencode/src/cli/cmd/tui/context/args.tsx b/packages/opencode/src/cli/cmd/tui/context/args.tsx index ffd43009a4..8a229ffaba 100644 --- a/packages/opencode/src/cli/cmd/tui/context/args.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/args.tsx @@ -6,6 +6,7 @@ export interface Args { prompt?: string continue?: boolean sessionID?: string + fork?: boolean } export const { use: useArgs, provider: ArgsProvider } = createSimpleContext({ diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 0571426854..0652502e0e 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -64,6 +64,11 @@ export const TuiThreadCommand = cmd({ type: "string", describe: "session id to continue", }) + .option("fork-session", { + alias: ["fork"], + describe: "fork the session before continuing (requires --continue or --session)", + type: "boolean", + }) .option("prompt", { type: "string", describe: "prompt to use", @@ -73,6 +78,11 @@ export const TuiThreadCommand = cmd({ describe: "agent to use", }), handler: async (args) => { + if (args.forkSession && !args.continue && !args.session) { + UI.error("--fork-session requires --continue or --session") + process.exit(1) + } + // Resolve relative paths against PWD to preserve behavior when using --cwd flag const baseCwd = process.env.PWD ?? process.cwd() const cwd = args.project ? path.resolve(baseCwd, args.project) : process.cwd() @@ -150,6 +160,7 @@ export const TuiThreadCommand = cmd({ agent: args.agent, model: args.model, prompt, + fork: args.forkSession, }, onExit: async () => { await client.call("shutdown", undefined) From 47a1b03a506eb527efe4bdb25fc0bc782d3027b2 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Fri, 30 Jan 2026 01:18:25 -0500 Subject: [PATCH 04/10] add short -f alias for --fork-session in TUI command, remove --fork alias --- packages/opencode/src/cli/cmd/run.ts | 1 - packages/opencode/src/cli/cmd/tui/thread.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index b1c7f2a3d2..13c77a10ef 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -51,7 +51,6 @@ export const RunCommand = cmd({ type: "string", }) .option("fork-session", { - alias: ["fork"], describe: "fork the session before continuing (requires --continue or --session)", type: "boolean", }) diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 0652502e0e..a2555304c2 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -65,7 +65,7 @@ export const TuiThreadCommand = cmd({ describe: "session id to continue", }) .option("fork-session", { - alias: ["fork"], + alias: ["f"], describe: "fork the session before continuing (requires --continue or --session)", type: "boolean", }) From 805d8194a2ba025590ff396cbefbc2003d8edbcb Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Fri, 30 Jan 2026 02:13:29 -0500 Subject: [PATCH 05/10] refactor: use const instead of let in run.ts for fork-session Replace verbose if/else if blocks with ternary expressions to match repo style guide preference for const over let. -8 lines total --- packages/opencode/src/cli/cmd/run.ts | 32 +++++++++++----------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 13c77a10ef..3bf89954ae 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -288,20 +288,16 @@ export const RunCommand = cmd({ const sdk = createOpencodeClient({ baseUrl: args.attach }) const sessionID = await (async () => { - let id: string | undefined - if (args.continue) { - const result = await sdk.session.list() - id = result.data?.find((s) => !s.parentID)?.id - } else if (args.session) { - id = args.session - } + const baseID = args.continue + ? (await sdk.session.list()).data?.find((s) => !s.parentID)?.id + : args.session - if (id && args.forkSession) { - const forked = await sdk.session.fork({ sessionID: id }) + if (baseID && args.forkSession) { + const forked = await sdk.session.fork({ sessionID: baseID }) return forked.data?.id } - if (id) return id + if (baseID) return baseID const title = args.title !== undefined @@ -372,20 +368,16 @@ export const RunCommand = cmd({ } const sessionID = await (async () => { - let id: string | undefined - if (args.continue) { - const result = await sdk.session.list() - id = result.data?.find((s) => !s.parentID)?.id - } else if (args.session) { - id = args.session - } + const baseID = args.continue + ? (await sdk.session.list()).data?.find((s) => !s.parentID)?.id + : args.session - if (id && args.forkSession) { - const forked = await sdk.session.fork({ sessionID: id }) + if (baseID && args.forkSession) { + const forked = await sdk.session.fork({ sessionID: baseID }) return forked.data?.id } - if (id) return id + if (baseID) return baseID const title = args.title !== undefined From a4928c35f3d70b6a136bb83d42b3d0d55583b37b Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Fri, 30 Jan 2026 02:50:21 -0500 Subject: [PATCH 06/10] feat: add error toast when fork-session fails --- packages/opencode/src/cli/cmd/tui/app.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 128ce8dbf6..5cb249da7c 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -252,6 +252,8 @@ function App() { sdk.client.session.fork({ sessionID: args.sessionID }).then((result) => { if (result.data?.id) { route.navigate({ type: "session", sessionID: result.data.id }) + } else { + toast.show({ message: "Failed to fork session", variant: "error" }) } }) } else { @@ -277,6 +279,8 @@ function App() { sdk.client.session.fork({ sessionID: match }).then((result) => { if (result.data?.id) { route.navigate({ type: "session", sessionID: result.data.id }) + } else { + toast.show({ message: "Failed to fork session", variant: "error" }) } }) } else { From 19c179a2ce0e37344fc32420a0283904b98026ef Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Fri, 30 Jan 2026 03:25:11 -0500 Subject: [PATCH 07/10] Fix --session + --fork race condition causing session to disappear When using --session with --fork, the app would briefly show the forked session then navigate to a blank screen. Root cause: race condition where reconcile overwrites the newly forked session from the store. Fix: Wait for sync.status to be "complete" before forking, ensuring session list is fully loaded before the fork operation. --- packages/opencode/src/cli/cmd/tui/app.tsx | 37 ++++++++++++++--------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 5cb249da7c..8256217d05 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -247,21 +247,12 @@ function App() { }) local.model.set({ providerID, modelID }, { recent: true }) } - if (args.sessionID) { - if (args.fork) { - sdk.client.session.fork({ sessionID: args.sessionID }).then((result) => { - if (result.data?.id) { - route.navigate({ type: "session", sessionID: result.data.id }) - } else { - toast.show({ message: "Failed to fork session", variant: "error" }) - } - }) - } else { - route.navigate({ - type: "session", - sessionID: args.sessionID, - }) - } + // Handle --session without --fork immediately (fork is handled in createEffect below) + if (args.sessionID && !args.fork) { + route.navigate({ + type: "session", + sessionID: args.sessionID, + }) } }) }) @@ -289,6 +280,22 @@ function App() { } }) + // Handle --session with --fork: wait for sync to be fully complete before forking + // (session list loads in non-blocking phase for --session, so we must wait for "complete" + // to avoid a race where reconcile overwrites the newly forked session) + let forked = false + createEffect(() => { + if (forked || sync.status !== "complete" || !args.sessionID || !args.fork) return + forked = true + sdk.client.session.fork({ sessionID: args.sessionID }).then((result) => { + if (result.data?.id) { + route.navigate({ type: "session", sessionID: result.data.id }) + } else { + toast.show({ message: "Failed to fork session", variant: "error" }) + } + }) + }) + createEffect( on( () => sync.status === "complete" && sync.data.provider.length === 0, From 69f4c91e05c44413560aa2a4316507ce65122a9c Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Fri, 30 Jan 2026 05:12:44 -0500 Subject: [PATCH 08/10] refactor: reorganize fork-session option properties and update description --- packages/opencode/src/cli/cmd/tui/thread.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index a2555304c2..3df0140e0f 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -66,8 +66,8 @@ export const TuiThreadCommand = cmd({ }) .option("fork-session", { alias: ["f"], - describe: "fork the session before continuing (requires --continue or --session)", type: "boolean", + describe: "fork the session when continuing (use with --continue or --session)", }) .option("prompt", { type: "string", From 1357e273e3138e9159d2d4be16145db8a92eb795 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Fri, 30 Jan 2026 11:11:44 -0500 Subject: [PATCH 09/10] feat: use -F alias for --fork-session in both commands for consistency --- packages/opencode/src/cli/cmd/run.ts | 1 + packages/opencode/src/cli/cmd/tui/thread.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 3bf89954ae..76fb44fe5c 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -51,6 +51,7 @@ export const RunCommand = cmd({ type: "string", }) .option("fork-session", { + alias: ["F"], describe: "fork the session before continuing (requires --continue or --session)", type: "boolean", }) diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 3df0140e0f..4240528739 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -65,7 +65,7 @@ export const TuiThreadCommand = cmd({ describe: "session id to continue", }) .option("fork-session", { - alias: ["f"], + alias: ["F"], type: "boolean", describe: "fork the session when continuing (use with --continue or --session)", }) From 09587612ed6abb7ea46d5576016da2abfa9c9b6e Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Fri, 30 Jan 2026 20:26:51 -0500 Subject: [PATCH 10/10] adjust shorthands --- packages/opencode/src/cli/cmd/run.ts | 2 +- packages/opencode/src/cli/cmd/tui/thread.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 76fb44fe5c..0a7f0b716a 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -51,7 +51,7 @@ export const RunCommand = cmd({ type: "string", }) .option("fork-session", { - alias: ["F"], + alias: ["fork"], describe: "fork the session before continuing (requires --continue or --session)", type: "boolean", }) diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 4240528739..8d7c0bd679 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -65,7 +65,7 @@ export const TuiThreadCommand = cmd({ describe: "session id to continue", }) .option("fork-session", { - alias: ["F"], + alias: ["fork"], type: "boolean", describe: "fork the session when continuing (use with --continue or --session)", })