fix(web): prevent duplicate session creation (#1222)

This commit is contained in:
SSU-WEI HUANG
2026-07-29 10:02:46 +08:00
committed by GitHub
parent 91022539d9
commit b281b931f5
2 changed files with 42 additions and 4 deletions
+32 -1
View File
@@ -13,6 +13,7 @@ const mocks = vi.hoisted(() => ({
spawnSession: vi.fn(),
onSuccess: vi.fn(),
notification: vi.fn(),
checkPathsExists: vi.fn(),
codexModelsLoading: false,
directoryExists: undefined as boolean | undefined
}))
@@ -44,7 +45,7 @@ vi.mock('@/hooks/useRecentPaths', () => ({
vi.mock('@/hooks/useMachinePathsExists', () => ({
useMachinePathsExists: () => ({
pathExistence: { 'C:\\repo': mocks.directoryExists },
checkPathsExists: async () => ({ 'C:\\repo': mocks.directoryExists })
checkPathsExists: mocks.checkPathsExists
})
}))
vi.mock('@/hooks/useDirectorySuggestions', () => ({
@@ -152,6 +153,8 @@ describe('NewSession launch preferences', () => {
mocks.spawnSession.mockReset()
mocks.onSuccess.mockReset()
mocks.notification.mockReset()
mocks.checkPathsExists.mockReset()
mocks.checkPathsExists.mockImplementation(async () => ({ 'C:\\repo': mocks.directoryExists }))
mocks.codexModelsLoading = false
mocks.directoryExists = true
savePreferredAgent('codex')
@@ -276,6 +279,34 @@ describe('NewSession launch preferences', () => {
})
})
it('spawns only once when Create is activated twice during directory validation', async () => {
let finishDirectoryCheck!: (result: Record<string, boolean>) => void
mocks.checkPathsExists.mockReturnValue(new Promise((resolve) => {
finishDirectoryCheck = resolve
}))
mocks.spawnSession.mockResolvedValue({ type: 'success', sessionId: 'session-1' })
render(
<NewSession
api={api}
machines={[machine]}
initialMachineId="machine-1"
initialDirectory="C:\\repo"
onSuccess={mocks.onSuccess}
onCancel={() => {}}
/>
)
const create = screen.getByTestId('create')
fireEvent.click(create)
fireEvent.click(create)
finishDirectoryCheck({ 'C:\\repo': true })
await waitFor(() => expect(mocks.onSuccess).toHaveBeenCalledWith('session-1'))
expect(mocks.checkPathsExists).toHaveBeenCalledTimes(1)
expect(mocks.spawnSession).toHaveBeenCalledTimes(1)
})
it('does not save changed launch settings when creation fails', async () => {
mocks.spawnSession.mockResolvedValue({ type: 'error', message: 'spawn failed' })
+10 -3
View File
@@ -147,7 +147,9 @@ export function NewSession(props: {
const [codexImportError, setCodexImportError] = useState<string | null>(null)
const [isImportingCodexSession, setIsImportingCodexSession] = useState(false)
const [isCodexImportDialogOpen, setIsCodexImportDialogOpen] = useState(false)
const isFormDisabled = Boolean(isPending || props.isLoading || isImportingCodexSession)
const [isCreating, setIsCreating] = useState(false)
const createInFlightRef = useRef(false)
const isFormDisabled = Boolean(isCreating || isPending || props.isLoading || isImportingCodexSession)
const worktreeInputRef = useRef<HTMLInputElement>(null)
const preserveRestoredDraftRef = useRef(false)
@@ -877,8 +879,10 @@ export function NewSession(props: {
}, [suggestions, selectedIndex, moveUp, moveDown, clearSuggestions, handleSuggestionSelect])
async function handleCreate() {
if (!machineId || !trimmedDirectory) return
if (!machineId || !trimmedDirectory || createInFlightRef.current) return
createInFlightRef.current = true
setIsCreating(true)
setError(null)
try {
const existsResult = await checkPathsExists([trimmedDirectory])
@@ -998,6 +1002,9 @@ export function NewSession(props: {
setIsImportingCodexSession(false)
haptic.notification('error')
setError(e instanceof Error ? e.message : 'Failed to create session')
} finally {
createInFlightRef.current = false
setIsCreating(false)
}
}
@@ -1217,7 +1224,7 @@ export function NewSession(props: {
) : null}
<ActionButtons
isPending={isPending || isImportingCodexSession}
isPending={isCreating || isPending || isImportingCodexSession}
canCreate={canCreate}
isDisabled={isFormDisabled}
createLabel={createLabel}