- 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 | isFirstMessage && envMode === "worktree" && !activeThread.worktreePath | 1401 | isFirstMessage && envMode === "worktree" && !activeThread.worktreePath |
| 1402 | ? activeThread.branch | 1402 | ? activeThread.branch |
| 1403 | : null; | 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 | const composerImagesSnapshot = [...composerImages]; | 1418 | const composerImagesSnapshot = [...composerImages]; |
| 1405 | const messageIdForSend = newMessageId(); | 1419 | const messageIdForSend = newMessageId(); |
| 1406 | const messageCreatedAt = new Date().toISOString(); | 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 | setThreadError(threadIdForSend, null); | 1441 | setThreadError(threadIdForSend, null); |
| 1441 | promptRef.current = ""; | 1442 | promptRef.current = ""; |
| 1442 | setPrompt(""); | 1443 | setPrompt(""); |
| @@ -1462,17 +1463,9 @@ export default function ChatView({ threadId }: ChatViewProps) { · 19 unmodified lines | |||
| 1462 | branch: result.worktree.branch, | 1463 | branch: result.worktree.branch, |
| 1463 | worktreePath: result.worktree.path, | 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 | const setupScript = setupProjectScript(activeProject.scripts); | 1466 | const setupScript = setupProjectScript(activeProject.scripts); |
| 1474 | if (setupScript) { | 1467 | if (setupScript) { |
| 1475 | await runProjectScript(setupScript, { | 1468 | void runProjectScript(setupScript, { |
| 1476 | cwd: result.worktree.path, | 1469 | cwd: result.worktree.path, |
| 1477 | worktreePath: result.worktree.path, | 1470 | worktreePath: result.worktree.path, |
| 1478 | rememberAsLastInvoked: false, | 1471 | rememberAsLastInvoked: false, |