From 8c91aa5570868091253be8f0ef333a46aa1b0450 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 26 Feb 2026 20:12:24 -0800 Subject: [PATCH] Prevent duplicate sends while composer request is in flight - Add `sendInFlightRef` guard in `ChatView` send handler - Set sending state before async work and always clear it in `finally` - Wrap early send flow in one `try/finally` to avoid double-submit races --- apps/web/src/components/ChatView.tsx | 37 +++++++++++----------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index b1966dd765b44e0b8dc59c4a92f638072b7d863c..501742c7ec8c181e2b834908d9ea2f5fc66797e0 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -1401,6 +1401,20 @@ export default function ChatView({ threadId }: ChatViewProps) { isFirstMessage && envMode === "worktree" && !activeThread.worktreePath ? activeThread.branch : null; + + // In worktree mode, require an explicit base branch so we don't silently + // fall back to local execution when branch selection is missing. + const shouldCreateWorktree = + isFirstMessage && envMode === "worktree" && !activeThread.worktreePath; + if (shouldCreateWorktree && !activeThread.branch) { + dispatch({ + type: "SET_ERROR", + threadId: threadIdForSend, + error: "Select a base branch before sending in New worktree mode.", + }); + return; + } + const composerImagesSnapshot = [...composerImages]; const messageIdForSend = newMessageId(); const messageCreatedAt = new Date().toISOString(); @@ -1424,19 +1438,6 @@ export default function ChatView({ threadId }: ChatViewProps) { }, ]); - // In worktree mode, require an explicit base branch so we don't silently - // fall back to local execution when branch selection is missing. - const shouldCreateWorktree = - activeThread.messages.length === 0 && envMode === "worktree" && !activeThread.worktreePath; - if (shouldCreateWorktree && !activeThread.branch) { - dispatch({ - type: "SET_ERROR", - threadId: activeThread.id, - error: "Select a base branch before sending in New worktree mode.", - }); - return; - } - setThreadError(threadIdForSend, null); promptRef.current = ""; setPrompt(""); @@ -1462,17 +1463,9 @@ export default function ChatView({ threadId }: ChatViewProps) { branch: result.worktree.branch, worktreePath: result.worktree.path, }); - // Keep local thread state in sync immediately so terminal drawer opens - // with the worktree cwd/env instead of briefly using the project root. - dispatch({ - type: "SET_THREAD_BRANCH", - threadId: threadIdForSend, - branch: result.worktree.branch, - worktreePath: result.worktree.path, - }); const setupScript = setupProjectScript(activeProject.scripts); if (setupScript) { - await runProjectScript(setupScript, { + void runProjectScript(setupScript, { cwd: result.worktree.path, worktreePath: result.worktree.path, rememberAsLastInvoked: false, -- 2.54.0