- 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
Unified · Split
| @@ -1401,6 +1401,20 @@ export default function ChatView({ threadId }: ChatViewProps) { · 1400 unmodified lines | ||
| 1401 | 1401 | isFirstMessage && envMode === "worktree" && !activeThread.worktreePath |
| 1402 | 1402 | ? activeThread.branch |
| 1403 | 1403 | : null; |
| 1404 | + | |
| 1405 | + // In worktree mode, require an explicit base branch so we don't silently | |
| 1406 | + // fall back to local execution when branch selection is missing. | |
| 1407 | + const shouldCreateWorktree = | |
| 1408 | + isFirstMessage && envMode === "worktree" && !activeThread.worktreePath; | |
| 1409 | + if (shouldCreateWorktree && !activeThread.branch) { | |
| 1410 | + dispatch({ | |
| 1411 | + type: "SET_ERROR", | |
| 1412 | + threadId: threadIdForSend, | |
| 1413 | + error: "Select a base branch before sending in New worktree mode.", | |
| 1414 | + }); | |
| 1415 | + return; | |
| 1416 | + } | |
| 1417 | + | |
| 1404 | 1418 | const composerImagesSnapshot = [...composerImages]; |
| 1405 | 1419 | const messageIdForSend = newMessageId(); |
| 1406 | 1420 | const messageCreatedAt = new Date().toISOString(); |
| @@ -1424,19 +1438,6 @@ export default function ChatView({ threadId }: ChatViewProps) { · 17 unmodified lines | ||
| 1424 | 1438 | }, |
| 1425 | 1439 | ]); |
| 1426 | 1440 | |
| 1427 | − // In worktree mode, require an explicit base branch so we don't silently | |
| 1428 | − // fall back to local execution when branch selection is missing. | |
| 1429 | − const shouldCreateWorktree = | |
| 1430 | − activeThread.messages.length === 0 && envMode === "worktree" && !activeThread.worktreePath; | |
| 1431 | − if (shouldCreateWorktree && !activeThread.branch) { | |
| 1432 | − dispatch({ | |
| 1433 | − type: "SET_ERROR", | |
| 1434 | − threadId: activeThread.id, | |
| 1435 | − error: "Select a base branch before sending in New worktree mode.", | |
| 1436 | − }); | |
| 1437 | − return; | |
| 1438 | − } | |
| 1439 | − | |
| 1440 | 1441 | setThreadError(threadIdForSend, null); |
| 1441 | 1442 | promptRef.current = ""; |
| 1442 | 1443 | setPrompt(""); |
| @@ -1462,17 +1463,9 @@ export default function ChatView({ threadId }: ChatViewProps) { · 19 unmodified lines | ||
| 1462 | 1463 | branch: result.worktree.branch, |
| 1463 | 1464 | worktreePath: result.worktree.path, |
| 1464 | 1465 | }); |
| 1465 | − // Keep local thread state in sync immediately so terminal drawer opens | |
| 1466 | − // with the worktree cwd/env instead of briefly using the project root. | |
| 1467 | − dispatch({ | |
| 1468 | − type: "SET_THREAD_BRANCH", | |
| 1469 | − threadId: threadIdForSend, | |
| 1470 | − branch: result.worktree.branch, | |
| 1471 | − worktreePath: result.worktree.path, | |
| 1472 | − }); | |
| 1473 | 1466 | const setupScript = setupProjectScript(activeProject.scripts); |
| 1474 | 1467 | if (setupScript) { |
| 1475 | − await runProjectScript(setupScript, { | |
| 1468 | + void runProjectScript(setupScript, { | |
| 1476 | 1469 | cwd: result.worktree.path, |
| 1477 | 1470 | worktreePath: result.worktree.path, |
| 1478 | 1471 | rememberAsLastInvoked: false, |