Skip to content

Move worktree bootstrap to the server and persist terminal launch context#1518

Merged
juliusmarminge merged 25 commits intomainfrom
t3code/persist-script-terminals
Apr 4, 2026
Merged

Move worktree bootstrap to the server and persist terminal launch context#1518
juliusmarminge merged 25 commits intomainfrom
t3code/persist-script-terminals

Conversation

@juliusmarminge
Copy link
Copy Markdown
Member

@juliusmarminge juliusmarminge commented Mar 29, 2026

Summary

  • Persist the terminal launch context so newly opened terminals keep the worktree cwd and env during first-send worktree setup flows.
  • Update draft-thread worktree creation to flush state immediately, so the terminal drawer opens with the correct worktree context instead of briefly using the project root.
  • Add a regression test covering local draft threads, worktree setup scripts, and terminal open/write requests.

Note

Medium Risk
Moves first-send thread/worktree setup and setup-script launching into the server’s orchestration dispatch path and extends terminal APIs/state with worktreePath, which could affect turn-start ordering, worktree creation, and terminal session behavior if edge cases are missed.

Overview
Moves worktree bootstrap from client to server. thread.turn.start now accepts an optional bootstrap payload (contracts updated) that lets the server create the thread, create/update the worktree, optionally run a setup script, then dispatch the final turn-start; failures can trigger cleanup (e.g. thread deletion) while non-fatal setup-script failures are recorded as activities.

Adds server-side setup-script runner and integrates it with PR worktrees. Introduces ProjectSetupScriptRunner (plus tests) that opens a deterministic setup terminal and writes the setup command; GitManager.preparePullRequestThread now accepts an optional threadId and runs the setup runner only when creating a new PR worktree, logging and continuing on runner errors.

Propagates and persists worktree launch context for terminals. Terminal open/restart inputs and session snapshots/events now include worktreePath; the web app adds a persisted terminalLaunchContextByThreadId and an applyTerminalEvent path to auto-register/activate server-started terminals and prevent leaking stale worktree env when launch context clears. Client ChatView removes local worktree creation/setup execution and updates PR dialog/mutations to pass threadId for server-owned setup.

Reviewed by Cursor Bugbot for commit 58e0ed7. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Move worktree bootstrap and setup script execution to the server on thread turn start

  • The client now sends a single thread.turn.start with a bootstrap payload (createThread, prepareWorktree, runSetupScript) instead of creating worktrees and running setup scripts locally.
  • The server's dispatchBootstrapTurnStart in ws.ts handles thread creation, git worktree setup, and setup script execution via the new ProjectSetupScriptRunner service before starting the turn.
  • worktreePath is propagated through terminal open/restart inputs, session snapshots, and terminal events so the correct working directory is surfaced in the UI.
  • ThreadTerminalLaunchContext is introduced in the terminal state store to persist per-thread cwd and worktreePath, replacing ad-hoc local state in ChatView.
  • Shared project script helpers (projectScriptCwd, projectScriptRuntimeEnv, setupProjectScript) are moved from the web app into @t3tools/shared/projectScripts for reuse by the server.
  • Risk: Bootstrap failures now trigger a server-side thread.delete cleanup; any failure in that cleanup path would leave orphaned threads.

Macroscope summarized 58e0ed7.

- keep terminal drawer attached to the created worktree for first-send draft setup scripts
- flush local thread and draft state updates before opening terminals
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9f77b227-5a57-41ce-a2d2-8a43f12c6534

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/persist-script-terminals

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Mar 29, 2026
- launch project setup scripts from the server after worktree prep
- keep setup terminal state tied to the thread and surface activity events
- add coverage for setup runner and bootstrap turn flow
@github-actions github-actions bot added size:XL 500-999 changed lines (additions + deletions). and removed size:M 30-99 changed lines (additions + deletions). labels Mar 29, 2026
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Stale "preparing-worktree" phase shows misleading UI state
    • Removed the stale 'preparing-worktree' send phase, its derived isPreparingWorktree flag, and all UI references since worktree preparation now happens server-side and the phase no longer corresponds to any client-side work.

Create PR

Or push these changes by commenting:

@cursor push 88bafdb9f0
Preview (88bafdb9f0)
diff --git a/apps/web/src/components/ChatView.logic.ts b/apps/web/src/components/ChatView.logic.ts
--- a/apps/web/src/components/ChatView.logic.ts
+++ b/apps/web/src/components/ChatView.logic.ts
@@ -75,7 +75,7 @@
   return previewUrls;
 }
 
-export type SendPhase = "idle" | "preparing-worktree" | "sending-turn";
+export type SendPhase = "idle" | "sending-turn";
 
 export interface PullRequestDialogState {
   initialReference: string | null;

diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -658,7 +658,6 @@
   const selectedModelForPicker = selectedModel;
   const phase = derivePhase(activeThread?.session ?? null);
   const isSendBusy = sendPhase !== "idle";
-  const isPreparingWorktree = sendPhase === "preparing-worktree";
   const isWorking = phase === "running" || isSendBusy || isConnecting || isRevertingCheckpoint;
   const nowIso = new Date(nowTick).toISOString();
   const activeWorkStartedAt = deriveActiveWorkStartedAt(
@@ -2573,7 +2572,7 @@
     }
 
     sendInFlightRef.current = true;
-    beginSendPhase(baseBranchForWorktree ? "preparing-worktree" : "sending-turn");
+    beginSendPhase("sending-turn");
 
     const composerImagesSnapshot = [...composerImages];
     const composerTerminalContextsSnapshot = [...sendableComposerTerminalContexts];
@@ -4006,11 +4005,6 @@
                         {activeContextWindow ? (
                           <ContextWindowMeter usage={activeContextWindow} />
                         ) : null}
-                        {isPreparingWorktree ? (
-                          <span className="text-muted-foreground/70 text-xs">
-                            Preparing worktree...
-                          </span>
-                        ) : null}
                         {activePendingProgress ? (
                           <div className="flex items-center gap-2">
                             {activePendingProgress.questionIndex > 0 ? (
@@ -4115,11 +4109,9 @@
                               aria-label={
                                 isConnecting
                                   ? "Connecting"
-                                  : isPreparingWorktree
-                                    ? "Preparing worktree"
-                                    : isSendBusy
-                                      ? "Sending"
-                                      : "Send message"
+                                  : isSendBusy
+                                    ? "Sending"
+                                    : "Send message"
                               }
                             >
                               {isConnecting || isSendBusy ? (

juliusmarminge and others added 2 commits March 30, 2026 10:24
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Terminal event handler opens drawer for all terminal events
    • Guarded the handler to only fire for setup terminals (IDs starting with 'setup-') and simplified worktreePath to always use event.snapshot.cwd since non-setup terminals are now excluded.

Create PR

Or push these changes by commenting:

@cursor push b28aa51f71
Preview (b28aa51f71)
diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx
--- a/apps/web/src/routes/__root.tsx
+++ b/apps/web/src/routes/__root.tsx
@@ -223,12 +223,15 @@
       domainEventFlushThrottler.maybeExecute();
     });
     const unsubTerminalEvent = api.terminal.onEvent((event) => {
-      if (event.type === "started" || event.type === "restarted") {
+      if (
+        (event.type === "started" || event.type === "restarted") &&
+        event.terminalId.startsWith("setup-")
+      ) {
         const threadId = ThreadId.makeUnsafe(event.threadId);
         ensureTerminal(threadId, event.terminalId, { open: true, active: true });
         setTerminalLaunchContext(threadId, {
           cwd: event.snapshot.cwd,
-          worktreePath: event.terminalId.startsWith("setup-") ? event.snapshot.cwd : null,
+          worktreePath: event.snapshot.cwd,
         });
       }
       const hasRunningSubprocess = terminalRunningSubprocessFromEvent(event);

You can send follow-ups to this agent here.

Co-authored-by: codex <codex@users.noreply.github.com>
@juliusmarminge juliusmarminge changed the title Persist terminal launch context for worktree drafts Move worktree bootstrap to the server and persist terminal launch context Mar 30, 2026
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Preparing-worktree indicator set and immediately reset
    • Moved beginLocalDispatch({ preparingWorktree: false }) from before dispatchCommand to after it, so the preparing-worktree indicator stays true during the server-side worktree creation.

Create PR

Or push these changes by commenting:

@cursor push c62a5d68cc
Preview (c62a5d68cc)
diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -2803,7 +2803,6 @@
         });
       }
 
-      beginLocalDispatch({ preparingWorktree: false });
       const turnAttachments = await turnAttachmentsPromise;
       const bootstrap =
         isLocalDraftThread || baseBranchForWorktree
@@ -2851,6 +2850,7 @@
         ...(bootstrap ? { bootstrap } : {}),
         createdAt: messageCreatedAt,
       });
+      beginLocalDispatch({ preparingWorktree: false });
       turnStartSucceeded = true;
     })().catch(async (err: unknown) => {
       if (

You can send follow-ups to this agent here.

@juliusmarminge
Copy link
Copy Markdown
Member Author

@cursor push c62a5d6

…renders during server-side worktree creation

Applied via @cursor push command
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Setup script failure aborts entire bootstrap and deletes thread
    • Removed the Effect.fail re-throw in the runSetupProgram error handler so setup script failures are logged as 'setup-script.failed' activities but no longer propagate and abort the bootstrap, consistent with the non-blocking behavior in GitManager.preparePullRequestThread.

Create PR

Or push these changes by commenting:

@cursor push 8a41f9f875
Preview (8a41f9f875)
diff --git a/apps/server/src/wsServer.ts b/apps/server/src/wsServer.ts
--- a/apps/server/src/wsServer.ts
+++ b/apps/server/src/wsServer.ts
@@ -733,10 +733,7 @@
                       worktreePath: targetWorktreePath,
                     },
                     tone: "error",
-                  }).pipe(
-                    Effect.ignoreCause({ log: false }),
-                    Effect.flatMap(() => Effect.fail(toBootstrapRouteRequestError(error))),
-                  ),
+                  }).pipe(Effect.ignoreCause({ log: false })),
                 ),
               );
           })()

You can send follow-ups to this agent here.

juliusmarminge and others added 3 commits March 31, 2026 00:52
Co-authored-by: codex <codex@users.noreply.github.com>
- Capture worktree path once for setup script events
- Log setup launch failures without masking the original cause
- Preserve thread cleanup behavior for non-interrupt failures
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Error catch conflates activity dispatch failure with script failure
    • Added Effect.ignoreCause({ log: true }) to the activity dispatch pipeline inside Effect.tap so that activity dispatch errors are logged but swallowed, preventing them from propagating to the outer Effect.catch which should only handle actual runForThread failures.

Create PR

Or push these changes by commenting:

@cursor push a3bc32b1ee
Preview (a3bc32b1ee)
diff --git a/apps/server/src/wsServer.ts b/apps/server/src/wsServer.ts
--- a/apps/server/src/wsServer.ts
+++ b/apps/server/src/wsServer.ts
@@ -674,11 +674,10 @@
                       payload,
                       tone: "info",
                     }),
-                  ]).pipe(Effect.asVoid);
+                  ]).pipe(Effect.asVoid, Effect.ignoreCause({ log: true }));
                 }),
                 Effect.catch((error) => {
-                  const detail =
-                    error instanceof Error ? error.message : "Unknown setup failure.";
+                  const detail = error instanceof Error ? error.message : "Unknown setup failure.";
                   return appendSetupScriptActivity({
                     threadId: command.threadId,
                     kind: "setup-script.failed",
@@ -692,14 +691,11 @@
                   }).pipe(
                     Effect.ignoreCause({ log: false }),
                     Effect.zipRight(
-                      Effect.logWarning(
-                        "bootstrap turn start failed to launch setup script",
-                        {
-                          threadId: command.threadId,
-                          worktreePath,
-                          detail,
-                        },
-                      ),
+                      Effect.logWarning("bootstrap turn start failed to launch setup script", {
+                        threadId: command.threadId,
+                        worktreePath,
+                        detail,
+                      }),
                     ),
                   );
                 }),

You can send follow-ups to this agent here.

juliusmarminge and others added 2 commits March 31, 2026 20:36
- replace Effect.zipRight with Effect.flatMap in bootstrap cleanup and setup failure handling
- keep the current server bootstrap behavior while restoring Effect v4 compatibility

Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Removed type-level guard on runServer requirements
    • Restored the satisfies Effect.Effect<never, any, ServerConfig> constraint on runServer to enforce at compile time that only ServerConfig is required by the CLI layer.

Create PR

Or push these changes by commenting:

@cursor push d77d59b58c
Preview (d77d59b58c)
diff --git a/apps/server/src/server.ts b/apps/server/src/server.ts
--- a/apps/server/src/server.ts
+++ b/apps/server/src/server.ts
@@ -230,4 +230,8 @@
 );
 
 // Important: Only `ServerConfig` should be provided by the CLI layer!!! Don't let other requirements leak into the launch layer.
-export const runServer = Layer.launch(makeServerLayer);
+export const runServer = Layer.launch(makeServerLayer) satisfies Effect.Effect<
+  never,
+  any,
+  ServerConfig
+>;

You can send follow-ups to this agent here.

Co-authored-by: codex <codex@users.noreply.github.com>
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp bot commented Apr 3, 2026

Approvability

Verdict: Needs human review

This PR makes significant architectural changes by moving worktree bootstrap and setup script execution from the client to the server. It introduces new orchestration command handling, a new ProjectSetupScriptRunner service, and persists terminal launch context. The scope and runtime behavior changes warrant human review to ensure the new server-side bootstrap flow is correct.

You can customize Macroscope's approvability policy. Learn more.

juliusmarminge and others added 3 commits April 2, 2026 20:38
Co-authored-by: codex <codex@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Error handler catches activity-dispatch failures as script failures
    • Added Effect.ignoreCause({ log: true }) to the activity dispatch pipe inside Effect.tap so that failures from appendSetupScriptActivity no longer propagate into the main error channel and get misattributed as setup-script failures by the downstream Effect.catch handler.

Create PR

Or push these changes by commenting:

@cursor push 98816167ce
Preview (98816167ce)
diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts
--- a/apps/server/src/ws.ts
+++ b/apps/server/src/ws.ts
@@ -174,7 +174,7 @@
                           payload,
                           tone: "info",
                         }),
-                      ]).pipe(Effect.asVoid);
+                      ]).pipe(Effect.asVoid, Effect.ignoreCause({ log: true }));
                     }),
                     Effect.catch((error) => {
                       const detail =

You can send follow-ups to this agent here.

- Apply terminal lifecycle events inside the terminal store
- Preserve launch context and buffered events for persisted terminals
- Factor shared terminal event append logic
- Keep launch context derivation consistent for start events
- Import cwd, env, and setup helpers from `@t3tools/shared/projectScripts`
- Remove redundant web-local wrappers and update tests
@github-actions github-actions bot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 3, 2026
- Record setup-script.started and setup-script.failed separately
- Avoid treating activity dispatch errors as launch failures
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Redundant reassignment of targetProjectCwd in bootstrap
    • Removed the dead reassignment of targetProjectCwd on line 278 since it was already initialized to the same value on line 123.
  • ✅ Fixed: Setup terminal worktree heuristic relies on naming convention
    • Extracted the "setup-" prefix into a shared SETUP_TERMINAL_ID_PREFIX constant in @t3tools/contracts used by both server and client, and removed the fragile effectiveWorktreePath fallback in PersistentThreadTerminalDrawer that could return the wrong path from a mismatched launch context.

Create PR

Or push these changes by commenting:

@cursor push 70ef0e6274
Preview (70ef0e6274)
diff --git a/apps/server/src/project/Layers/ProjectSetupScriptRunner.ts b/apps/server/src/project/Layers/ProjectSetupScriptRunner.ts
--- a/apps/server/src/project/Layers/ProjectSetupScriptRunner.ts
+++ b/apps/server/src/project/Layers/ProjectSetupScriptRunner.ts
@@ -1,3 +1,4 @@
+import { SETUP_TERMINAL_ID_PREFIX } from "@t3tools/contracts";
 import { projectScriptRuntimeEnv, setupProjectScript } from "@t3tools/shared/projectScripts";
 import { Effect, Layer } from "effect";
 
@@ -35,7 +36,7 @@
         } as const;
       }
 
-      const terminalId = input.preferredTerminalId ?? `setup-${script.id}`;
+      const terminalId = input.preferredTerminalId ?? `${SETUP_TERMINAL_ID_PREFIX}${script.id}`;
       const cwd = input.worktreePath;
       const env = projectScriptRuntimeEnv({
         project: { cwd: project.workspaceRoot },

diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts
--- a/apps/server/src/ws.ts
+++ b/apps/server/src/ws.ts
@@ -275,7 +275,6 @@
               newBranch: bootstrap.prepareWorktree.branch,
               path: null,
             });
-            targetProjectCwd = bootstrap.prepareWorktree.projectCwd;
             targetWorktreePath = worktree.worktree.path;
             yield* orchestrationEngine.dispatch({
               type: "thread.meta.update",

diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -446,11 +446,8 @@
     if (launchContext?.worktreePath) {
       return launchContext.worktreePath;
     }
-    if (launchContext && terminalState.activeTerminalId.startsWith("setup-")) {
-      return launchContext.cwd;
-    }
     return worktreePath;
-  }, [launchContext, terminalState.activeTerminalId, worktreePath]);
+  }, [launchContext, worktreePath]);
   const cwd = useMemo(
     () =>
       launchContext?.cwd ??

diff --git a/apps/web/src/terminalStateStore.ts b/apps/web/src/terminalStateStore.ts
--- a/apps/web/src/terminalStateStore.ts
+++ b/apps/web/src/terminalStateStore.ts
@@ -5,7 +5,7 @@
  * API constrained to store actions/selectors.
  */
 
-import { ThreadId, type TerminalEvent } from "@t3tools/contracts";
+import { SETUP_TERMINAL_ID_PREFIX, ThreadId, type TerminalEvent } from "@t3tools/contracts";
 import { create } from "zustand";
 import { createJSONStorage, persist } from "zustand/middleware";
 import { resolveStorage } from "./lib/storage";
@@ -281,7 +281,7 @@
 ): ThreadTerminalLaunchContext {
   return {
     cwd: event.snapshot.cwd,
-    worktreePath: event.terminalId.startsWith("setup-") ? event.snapshot.cwd : null,
+    worktreePath: event.terminalId.startsWith(SETUP_TERMINAL_ID_PREFIX) ? event.snapshot.cwd : null,
   };
 }
 

diff --git a/packages/contracts/src/terminal.ts b/packages/contracts/src/terminal.ts
--- a/packages/contracts/src/terminal.ts
+++ b/packages/contracts/src/terminal.ts
@@ -2,6 +2,7 @@
 import { TrimmedNonEmptyString } from "./baseSchemas";
 
 export const DEFAULT_TERMINAL_ID = "default";
+export const SETUP_TERMINAL_ID_PREFIX = "setup-";
 
 const TrimmedNonEmptyStringSchema = TrimmedNonEmptyString;
 const TerminalColsSchema = Schema.Int.check(Schema.isGreaterThanOrEqualTo(20)).check(

You can send follow-ups to this agent here.

- Thread worktree path through contracts, server, and UI
- Preserve setup terminal context across snapshots and restarts
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Local dispatch state set after await, not before
    • Moved beginLocalDispatch({ preparingWorktree: false }) to before await api.orchestration.dispatchCommand(...) so the busy/preparing state transition happens before the server round-trip, matching the old code's ordering and ensuring UI feedback throughout the send flow.

Create PR

Or push these changes by commenting:

@cursor push 029a6025e2
Preview (029a6025e2)
diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -3072,6 +3072,7 @@
                 : {}),
             }
           : undefined;
+      beginLocalDispatch({ preparingWorktree: false });
       await api.orchestration.dispatchCommand({
         type: "thread.turn.start",
         commandId: newCommandId(),
@@ -3089,7 +3090,6 @@
         ...(bootstrap ? { bootstrap } : {}),
         createdAt: messageCreatedAt,
       });
-      beginLocalDispatch({ preparingWorktree: false });
       turnStartSucceeded = true;
     })().catch(async (err: unknown) => {
       if (

You can send follow-ups to this agent here.

- carry worktreePath into reopened terminal snapshots
- show sending state while bootstrap dispatch is pending
- add regression coverage for reopened exited sessions
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Truthiness check drops null worktreePath from launch context
    • Changed the truthiness check if (launchContext?.worktreePath) to if (launchContext) so that a null worktreePath from the launch context is correctly preserved instead of falling through to the server thread's worktreePath.

Create PR

Or push these changes by commenting:

@cursor push 60a6e5c97f
Preview (60a6e5c97f)
diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -443,7 +443,7 @@
   const [localFocusRequestId, setLocalFocusRequestId] = useState(0);
   const worktreePath = serverThread?.worktreePath ?? draftThread?.worktreePath ?? null;
   const effectiveWorktreePath = useMemo(() => {
-    if (launchContext?.worktreePath) {
+    if (launchContext) {
       return launchContext.worktreePath;
     }
     return worktreePath;

You can send follow-ups to the cloud agent here.

- pass `worktreePath` through `resolveWsRpc`
- default missing values to `null`
- Avoid leaking the thread worktree into terminal env when launch context clears it
- Add regression coverage for terminal open env
- Clear persisted terminal state before each test
- Avoid cross-test leakage from prior browser sessions
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Redundant ternary branch always evaluates to null
    • Simplified the nested ternary typeof body.worktreePath === "string" ? body.worktreePath : body.worktreePath === null ? null : null to typeof body.worktreePath === "string" ? body.worktreePath : null, removing the redundant inner branch.

Create PR

Or push these changes by commenting:

@cursor push dc3944a26c
Preview (dc3944a26c)
diff --git a/apps/web/src/components/ChatView.browser.tsx b/apps/web/src/components/ChatView.browser.tsx
--- a/apps/web/src/components/ChatView.browser.tsx
+++ b/apps/web/src/components/ChatView.browser.tsx
@@ -683,12 +683,7 @@
       threadId: typeof body.threadId === "string" ? body.threadId : THREAD_ID,
       terminalId: typeof body.terminalId === "string" ? body.terminalId : "default",
       cwd: typeof body.cwd === "string" ? body.cwd : "/repo/project",
-      worktreePath:
-        typeof body.worktreePath === "string"
-          ? body.worktreePath
-          : body.worktreePath === null
-            ? null
-            : null,
+      worktreePath: typeof body.worktreePath === "string" ? body.worktreePath : null,
       status: "running",
       pid: 123,
       history: "",

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 58e0ed7. Configure here.

? body.worktreePath
: body.worktreePath === null
? null
: null,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant ternary branch always evaluates to null

Low Severity

The worktreePath value uses a nested ternary where both the body.worktreePath === null and the default fallback branches resolve to null. The expression body.worktreePath === null ? null : null is logically equivalent to just null, making the inner ternary redundant. The whole expression simplifies to typeof body.worktreePath === "string" ? body.worktreePath : null.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 58e0ed7. Configure here.

@juliusmarminge juliusmarminge merged commit 8515f02 into main Apr 4, 2026
13 checks passed
@juliusmarminge juliusmarminge deleted the t3code/persist-script-terminals branch April 4, 2026 02:49
aaditagrawal pushed a commit to aaditagrawal/t3code that referenced this pull request Apr 4, 2026
…text (pingdotgg#1518)

Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
aaditagrawal added a commit to aaditagrawal/t3code that referenced this pull request Apr 5, 2026
…-bootstrap

Merge upstream: Move worktree bootstrap to server and persist terminal launch context (pingdotgg#1518)
gigq pushed a commit to gigq/t3code that referenced this pull request Apr 6, 2026
…text (pingdotgg#1518)

Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Chrono-byte pushed a commit to Chrono-byte/t3code that referenced this pull request Apr 7, 2026
…text (pingdotgg#1518)

Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants