fix(workspaces): complete a worktree create when a post-create step throws (#20175)

* fix(workspaces): complete a worktree create when a post-create step throws

executeWorktreeCreation's try/catch ends once createWorktree resolves, and all
three callers fire it with a bare void and no .catch. completeWorktreeCreation
is the only thing that removes the pending creation, so a throw in that tail
left pendingWorktreeCreations and activePendingCreationId set: the creation
surface stayed up, workspaceChromeActive went false, and the finished workspace
rendered no tab chrome while its panes mounted invisibly behind the panel. It
only cleared when the user switched workspaces, because setActiveWorktree nulls
the pointer. Silently -- no toast, no error state.

activateAndRevealWorktree, ensureWorktreeHasInitialTerminal and
ensureWebRuntimeWorktreeTerminalAfterWake are all synchronous with no internal
guard; launchStructuredWorktreeSession guards only its awaited launch, and that
catch's comment already names this stranding hazard.

The worktree exists past that point, so each follow-up step is now guarded
individually and falls back to the values the skip paths already used; control
flow always reaches completion. The structured-launch cancelled/visibility
returns keep their semantics, and a throw there is treated as a failed launch,
matching what that module already returns for 'failed'. A .catch backstop on
the three call sites turns anything that still escapes -- including
prepareRequestForCreate, whose VM await has try/finally with no catch -- into a
visible error state plus toast.

Ablated: with the guards removed the new suite fails 4 of 5, the survivor being
the no-throw control.

* fix(workspaces): recover terminal after partial activation

* fix(workspaces): preserve stamped launch tab on recovery

* test(workspaces): cover recovered agent tab delivery

* test(workspaces): name recovered agent delivery coverage

---------

Co-authored-by: Merge Sim <sim@local>
(cherry picked from commit 70a588c8bf)
This commit is contained in:
Brennan Benson
2026-09-11 18:13:44 -07:00
committed by Merge Sim
parent fff04a8d88
commit 92b43c6fa3
3 changed files with 524 additions and 48 deletions
@@ -19,7 +19,10 @@ import type { WorktreeCreationRequest } from '@/lib/pending-worktree-creation'
import { createBrowserUuid } from '@/lib/browser-uuid'
import { resolveBackendDraftStartup } from '@/lib/worktree-draft-startup-view-mode'
import { buildWorktreeCreationStartupOpt } from '@/lib/worktree-creation-flow-startup'
import { launchStructuredWorktreeSession } from '@/lib/worktree-creation-structured-session'
import {
launchStructuredWorktreeSession,
type WorktreeCreationStructuredSessionResult
} from '@/lib/worktree-creation-structured-session'
import { completeWorktreeCreation } from '@/lib/worktree-creation-completion'
import { markStructuredWorktreeLaunchUnconfirmed } from '@/lib/worktree-creation-structured-recovery'
import { ensureWebRuntimeWorktreeTerminalAfterWake } from '@/lib/web-runtime-worktree-terminal-after-wake'
@@ -164,28 +167,41 @@ export async function executeWorktreeCreation(
(completionState.activeView === 'terminal' &&
completionState.activePendingCreationId === null))
// Why: the worktree exists past this point and nothing awaits this caller, so
// each follow-up step is best-effort — an escaped throw would strand the
// creation surface over the finished workspace instead of reaching completion.
let activation: ActivateAndRevealResult | false = false
let primaryTabId: string | null
let primaryTabId: string | null = null
if (shouldActivateOnCompletion && !structuredLaunch) {
activation = activateAndRevealWorktree(worktree.id, {
sidebarRevealBehavior: 'auto',
...(preparedRequest.agent !== null ? { agent: preparedRequest.agent } : {}),
...(result.setup ? { setup: result.setup } : {}),
...(result.defaultTabs ? { defaultTabs: result.defaultTabs } : {}),
...(startupOpt ? { startup: startupOpt } : {}),
...(preparedRequest.issueCommand ? { issueCommand: preparedRequest.issueCommand } : {}),
...(backendSpawned ? { backendStartupTerminalSpawned: true } : {})
})
primaryTabId = activation === false ? null : activation.primaryTabId
} else {
// Keep chat creation on its pending surface until the session is ready.
const hasExplicitTerminalWork = Boolean(
startupOpt || result.setup || preparedRequest.issueCommand || result.defaultTabs
)
primaryTabId =
preparedRequest.agent !== null && !hasExplicitTerminalWork
? null
: ensureWorktreeHasInitialTerminal(
try {
activation = activateAndRevealWorktree(worktree.id, {
sidebarRevealBehavior: 'auto',
...(preparedRequest.agent !== null ? { agent: preparedRequest.agent } : {}),
...(result.setup ? { setup: result.setup } : {}),
...(result.defaultTabs ? { defaultTabs: result.defaultTabs } : {}),
...(startupOpt ? { startup: startupOpt } : {}),
...(preparedRequest.issueCommand ? { issueCommand: preparedRequest.issueCommand } : {}),
...(backendSpawned ? { backendStartupTerminalSpawned: true } : {})
})
primaryTabId = activation === false ? null : activation.primaryTabId
} catch (error) {
console.error('worktree create: activate-and-reveal failed', worktree.id, error)
// Activation can publish the worktree before a later step throws. Do not
// infer a primary tab from default-tab ordering; only a fresh seed may
// return one here.
const stateAfterActivationFailure = useAppStore.getState()
const existingTabs = stateAfterActivationFailure.tabsByWorktree[worktree.id] ?? []
const launchAgent = startupOpt?.launchAgent ?? preparedRequest.agent
const verifiedLaunchTabId =
result.startupTerminal?.tabId ??
(launchAgent ? existingTabs.find((tab) => tab.launchAgent === launchAgent)?.id : undefined)
if (verifiedLaunchTabId) {
// Startup terminal ids and stamped agent tabs are the only safe primary
// ids when activation returned no result.
primaryTabId = verifiedLaunchTabId
} else if (existingTabs.length === 0) {
try {
primaryTabId = ensureWorktreeHasInitialTerminal(
useAppStore.getState(),
worktree.id,
startupOpt,
@@ -193,17 +209,67 @@ export async function executeWorktreeCreation(
preparedRequest.issueCommand,
result.defaultTabs,
{
activateCreatedTabs: false,
...(preparedRequest.agent !== null ? { callerProvidesSurface: true } : {}),
...(backendSpawned ? { backendStartupTerminalSpawned: true } : {})
}
)
} catch (recoveryError) {
console.error(
'worktree create: activation recovery seeding failed',
worktree.id,
recoveryError
)
}
}
if (!backendSpawned) {
try {
ensureWebRuntimeWorktreeTerminalAfterWake(worktree.id, {
startup: startupOpt,
agent: preparedRequest.agent
})
} catch (recoveryError) {
console.error(
'worktree create: activation recovery after-wake seeding failed',
worktree.id,
recoveryError
)
}
}
}
} else {
// Keep chat creation on its pending surface until the session is ready.
const hasExplicitTerminalWork = Boolean(
startupOpt || result.setup || preparedRequest.issueCommand || result.defaultTabs
)
if (preparedRequest.agent === null || hasExplicitTerminalWork) {
try {
primaryTabId = ensureWorktreeHasInitialTerminal(
useAppStore.getState(),
worktree.id,
startupOpt,
result.setup,
preparedRequest.issueCommand,
result.defaultTabs,
{
activateCreatedTabs: false,
...(preparedRequest.agent !== null ? { callerProvidesSurface: true } : {}),
...(backendSpawned ? { backendStartupTerminalSpawned: true } : {})
}
)
} catch (error) {
console.error('worktree create: initial terminal seeding failed', worktree.id, error)
}
}
if (!structuredLaunch && !backendSpawned) {
ensureWebRuntimeWorktreeTerminalAfterWake(worktree.id, {
startup: startupOpt,
agent: preparedRequest.agent,
activate: false
})
try {
ensureWebRuntimeWorktreeTerminalAfterWake(worktree.id, {
startup: startupOpt,
agent: preparedRequest.agent,
activate: false
})
} catch (error) {
console.error('worktree create: after-wake terminal seeding failed', worktree.id, error)
}
}
}
@@ -213,25 +279,34 @@ export async function executeWorktreeCreation(
agentLaunchRoute === 'structured-native-chat' &&
isAgentSessionHandleProvider(preparedRequest.agent)
) {
const structuredSession = await launchStructuredWorktreeSession({
creationId,
request: preparedRequest,
agentLaunchRoute,
worktreeId: worktree.id,
shouldActivateOnCompletion,
fallbackStartupOpt,
activation,
primaryTabId
})
structuredLaunchAccepted = structuredSession.accepted
activation = structuredSession.activation
primaryTabId = structuredSession.primaryTabId
if (structuredSession.cancelled) {
return
let structuredSession: WorktreeCreationStructuredSessionResult | null = null
try {
structuredSession = await launchStructuredWorktreeSession({
creationId,
request: preparedRequest,
agentLaunchRoute,
worktreeId: worktree.id,
shouldActivateOnCompletion,
fallbackStartupOpt,
activation,
primaryTabId
})
} catch (error) {
// Why: plan.launch is guarded inside, but its sync prologue is not; treat
// an escaped throw like a failed launch (accepted) and still complete.
console.error('worktree create: structured session launch failed', worktree.id, error)
}
if (structuredSession.visibilityUnknown) {
markStructuredWorktreeLaunchUnconfirmed(creationId, worktree.id)
return
if (structuredSession) {
structuredLaunchAccepted = structuredSession.accepted
activation = structuredSession.activation
primaryTabId = structuredSession.primaryTabId
if (structuredSession.cancelled) {
return
}
if (structuredSession.visibilityUnknown) {
markStructuredWorktreeLaunchUnconfirmed(creationId, worktree.id)
return
}
}
}
@@ -0,0 +1,377 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type {
PendingWorktreeCreation,
WorktreeCreationRequest
} from '@/lib/pending-worktree-creation'
import { shouldShowWorktreeCreationSurface } from '@/lib/worktree-creation-surface'
// Guards executeWorktreeCreation's post-create tail: callers fire and forget,
// so a throw after createWorktree succeeds must be contained per-step and the
// creation must still reach completeWorktreeCreation, which tears the creation
// surface down. Also covers the caller-side .catch() backstop: a rejection that
// still escapes (e.g. pre-create preparation) becomes a visible error state
// plus toast instead of a panel silently stuck at "creating".
type TestActiveView = 'terminal' | 'tasks'
const store = {
settings: {
activeRuntimeEnvironmentId: null as string | null,
experimentalNativeChat: undefined as boolean | undefined,
openAgentTabsInChatByDefault: undefined as boolean | undefined
},
activeView: 'terminal' as TestActiveView,
activePendingCreationId: 'creation-1' as string | null,
repos: [] as { id: string; connectionId: string | null }[],
pendingWorktreeCreations: {} as Record<string, PendingWorktreeCreation>,
beginPendingWorktreeCreation: vi.fn((entry: PendingWorktreeCreation) => {
store.pendingWorktreeCreations[entry.creationId] = entry
store.activePendingCreationId = entry.creationId
}),
updatePendingWorktreeCreation: vi.fn(
(creationId: string, patch: Partial<PendingWorktreeCreation>) => {
const entry = store.pendingWorktreeCreations[creationId]
if (entry) {
store.pendingWorktreeCreations[creationId] = { ...entry, ...patch }
}
}
),
// Mirrors pending-worktree-creation.ts: drop the entry and the active pointer.
removePendingWorktreeCreation: vi.fn((creationId: string) => {
delete store.pendingWorktreeCreations[creationId]
if (store.activePendingCreationId === creationId) {
store.activePendingCreationId = null
}
}),
setActivePendingWorktreeCreation: vi.fn((creationId: string | null) => {
store.activePendingCreationId = creationId
}),
setActiveView: vi.fn((view: TestActiveView) => {
store.activeView = view
}),
setSidebarOpen: vi.fn(),
updateWorktreeMeta: vi.fn(),
createWorktree: vi.fn(),
tabsByWorktree: {} as Record<string, { id: string; launchAgent?: string }[]>,
unifiedTabsByWorktree: {}
}
vi.mock('@/store', () => ({
useAppStore: {
getState: () => store
}
}))
vi.mock('sonner', () => ({
toast: { error: vi.fn() }
}))
vi.mock('@/lib/worktree-activation', () => ({
activateAndRevealWorktree: vi.fn()
}))
vi.mock('@/lib/worktree-initial-terminal-seeding', () => ({
ensureWorktreeHasInitialTerminal: vi.fn()
}))
vi.mock('@/lib/web-runtime-worktree-terminal-after-wake', () => ({
ensureWebRuntimeWorktreeTerminalAfterWake: vi.fn()
}))
vi.mock('@/lib/workspace-activation-terminal-focus', () => ({
queueWorkspaceActivationTerminalFocus: vi.fn()
}))
vi.mock('@/lib/new-workspace', () => ({
ensureAgentStartupInTerminal: vi.fn()
}))
vi.mock('@/lib/worktree-creation-agent-seeds', () => ({
seedAgentTabStateAfterWorktreeCreate: vi.fn()
}))
vi.mock('@/lib/ephemeral-vm-workspace-target', () => ({
prepareEphemeralVmWorkspaceTarget: vi.fn()
}))
vi.mock('@/lib/ephemeral-vm-worktree-creation', () => ({
prepareRequestForCreate: vi.fn(
async (_creationId: string, request: WorktreeCreationRequest) => request
),
attachEphemeralVmRuntimeToWorkspace: vi.fn(async () => undefined),
cleanupEphemeralVmRuntimeForFailedCreate: vi.fn(async () => undefined)
}))
vi.mock('@/lib/worktree-creation-structured-recovery', () => ({
markStructuredWorktreeLaunchUnconfirmed: vi.fn(),
retryStructuredWorktreeLaunch: vi.fn()
}))
import { toast } from 'sonner'
import { activateAndRevealWorktree } from '@/lib/worktree-activation'
import { ensureWorktreeHasInitialTerminal } from '@/lib/worktree-initial-terminal-seeding'
import { ensureWebRuntimeWorktreeTerminalAfterWake } from '@/lib/web-runtime-worktree-terminal-after-wake'
import { ensureAgentStartupInTerminal } from '@/lib/new-workspace'
import { prepareRequestForCreate } from '@/lib/ephemeral-vm-worktree-creation'
import { executeWorktreeCreation } from './worktree-creation-flow-execute'
import { runBackgroundWorktreeCreation } from './worktree-creation-flow'
function makeRequest(overrides: Partial<WorktreeCreationRequest> = {}): WorktreeCreationRequest {
return {
repoId: 'repo-1',
name: 'feature',
setupDecision: 'inherit',
agent: null,
pendingFirstAgentMessageRename: false,
note: '',
startupPlan: null,
quickPrompt: '',
quickTelemetry: null,
...overrides
} as WorktreeCreationRequest
}
function seedPendingCreation(request: WorktreeCreationRequest): void {
store.pendingWorktreeCreations = {
'creation-1': {
creationId: 'creation-1',
phase: 'fetching',
status: 'creating',
startedAt: 1,
indeterminate: false,
loaderVisible: true,
request
}
}
store.activePendingCreationId = 'creation-1'
}
function surfaceInput(activeView: TestActiveView): {
activeView: TestActiveView
activePendingCreationId: string | null
hasActivePendingCreation: boolean
} {
return {
activeView,
activePendingCreationId: store.activePendingCreationId,
hasActivePendingCreation:
store.activePendingCreationId !== null &&
store.pendingWorktreeCreations[store.activePendingCreationId] !== undefined
}
}
beforeEach(() => {
// resetAllMocks: implementations from prior tests (the injected throws) must not leak.
vi.resetAllMocks()
vi.spyOn(console, 'error').mockImplementation(() => undefined)
store.activeView = 'terminal'
store.repos = [{ id: 'repo-1', connectionId: null }]
store.tabsByWorktree = {}
store.pendingWorktreeCreations = {}
store.activePendingCreationId = null
store.createWorktree.mockResolvedValue({
worktree: { id: 'wt-1', repoId: 'repo-1' }
})
})
describe('a throw after createWorktree succeeds no longer strands the creation surface', () => {
it('activating branch: a throw in activateAndRevealWorktree recovers a terminal and completes', async () => {
const request = makeRequest()
seedPendingCreation(request)
vi.mocked(ensureWorktreeHasInitialTerminal).mockReturnValue('recovered-tab')
vi.mocked(activateAndRevealWorktree).mockImplementation(() => {
throw new Error('activation exploded')
})
await executeWorktreeCreation('creation-1', request)
expect(console.error).toHaveBeenCalledWith(
'worktree create: activate-and-reveal failed',
'wt-1',
expect.any(Error)
)
expect(ensureWorktreeHasInitialTerminal).toHaveBeenCalledWith(
store,
'wt-1',
undefined,
undefined,
undefined,
undefined,
{}
)
// Contained: completion still tears the surface down.
expect(store.removePendingWorktreeCreation).toHaveBeenCalledWith('creation-1', {
cleanupVm: false
})
expect(store.pendingWorktreeCreations['creation-1']).toBeUndefined()
expect(store.activePendingCreationId).toBeNull()
expect(shouldShowWorktreeCreationSurface(surfaceInput('terminal'))).toBe(false)
})
it('activating branch: leaves existing default tabs untouched after a partial failure', async () => {
const request = makeRequest({ issueCommand: { command: 'echo setup' } })
seedPendingCreation(request)
store.tabsByWorktree = { 'wt-1': [{ id: 'existing-tab' }] }
vi.mocked(activateAndRevealWorktree).mockImplementation(() => {
throw new Error('reveal exploded after tab creation')
})
await executeWorktreeCreation('creation-1', request)
expect(ensureWorktreeHasInitialTerminal).not.toHaveBeenCalled()
expect(store.removePendingWorktreeCreation).toHaveBeenCalledWith('creation-1', {
cleanupVm: false
})
})
it('activating branch: routes draft and follow-up delivery to the stamped agent tab', async () => {
const request = makeRequest({
agent: 'codex',
startupPlan: {
agent: 'codex',
launchCommand: 'codex',
expectedProcess: 'codex',
draftPrompt: 'draft context',
followupPrompt: 'follow-up context',
launchConfig: { agentArgs: '', agentEnv: {} }
}
})
seedPendingCreation(request)
store.tabsByWorktree = {
'wt-1': [{ id: 'default-tab' }, { id: 'agent-tab', launchAgent: 'codex' }]
}
vi.mocked(activateAndRevealWorktree).mockImplementation(() => {
throw new Error('reveal exploded after default tabs were created')
})
await executeWorktreeCreation('creation-1', request)
expect(ensureWorktreeHasInitialTerminal).not.toHaveBeenCalled()
expect(ensureAgentStartupInTerminal).toHaveBeenCalledWith(
expect.objectContaining({ primaryTabId: 'agent-tab' })
)
})
it('background branch: a throw in after-wake seeding is contained after tabs are seeded', async () => {
// User left the terminal view mid-create, so the non-activating branch runs.
store.activeView = 'tasks'
const request = makeRequest()
seedPendingCreation(request)
vi.mocked(ensureWorktreeHasInitialTerminal).mockReturnValue('tab-1')
vi.mocked(ensureWebRuntimeWorktreeTerminalAfterWake).mockImplementation(() => {
throw new Error('after-wake exploded')
})
await executeWorktreeCreation('creation-1', request)
// Tabs were seeded for the new worktree...
expect(ensureWorktreeHasInitialTerminal).toHaveBeenCalledWith(
store,
'wt-1',
undefined,
undefined,
undefined,
undefined,
expect.objectContaining({ activateCreatedTabs: false })
)
expect(console.error).toHaveBeenCalledWith(
'worktree create: after-wake terminal seeding failed',
'wt-1',
expect.any(Error)
)
// ...and the creation still completed instead of stranding the entry.
expect(store.removePendingWorktreeCreation).toHaveBeenCalledWith('creation-1', {
cleanupVm: false
})
expect(store.pendingWorktreeCreations['creation-1']).toBeUndefined()
expect(store.activePendingCreationId).toBeNull()
expect(shouldShowWorktreeCreationSurface(surfaceInput('terminal'))).toBe(false)
})
it('concurrent create: a throw completing a backgrounded creation still tears its entry down', async () => {
// A second submitted create repointed activePendingCreationId, so this
// creation's completion takes the non-activating branch on the terminal view.
store.activeView = 'terminal'
const request = makeRequest()
seedPendingCreation(request)
store.activePendingCreationId = 'creation-2'
store.createWorktree.mockResolvedValue({
worktree: { id: 'wt-1', repoId: 'repo-1' },
setup: { runnerScriptPath: '/tmp/setup.sh' }
})
vi.mocked(ensureWorktreeHasInitialTerminal).mockReturnValue('tab-1')
vi.mocked(ensureWebRuntimeWorktreeTerminalAfterWake).mockImplementation(() => {
throw new Error('after-wake exploded')
})
await executeWorktreeCreation('creation-1', request)
// Blank terminal + Setup tab are seeded by this one synchronous call.
expect(activateAndRevealWorktree).not.toHaveBeenCalled()
expect(ensureWorktreeHasInitialTerminal).toHaveBeenCalledWith(
store,
'wt-1',
undefined,
{ runnerScriptPath: '/tmp/setup.sh' },
undefined,
undefined,
expect.objectContaining({ activateCreatedTabs: false })
)
// The entry is gone; the pointer stays on the other in-flight creation.
expect(store.pendingWorktreeCreations['creation-1']).toBeUndefined()
expect(store.activePendingCreationId).toBe('creation-2')
expect(shouldShowWorktreeCreationSurface(surfaceInput('terminal'))).toBe(false)
})
it('control: with no throw the same flow completes and tears the surface down', async () => {
store.activeView = 'tasks'
const request = makeRequest()
seedPendingCreation(request)
vi.mocked(ensureWorktreeHasInitialTerminal).mockReturnValue('tab-1')
await executeWorktreeCreation('creation-1', request)
expect(store.removePendingWorktreeCreation).toHaveBeenCalledWith('creation-1', {
cleanupVm: false
})
expect(store.pendingWorktreeCreations['creation-1']).toBeUndefined()
expect(store.activePendingCreationId).toBeNull()
expect(shouldShowWorktreeCreationSurface(surfaceInput('terminal'))).toBe(false)
})
it('backstop: a rejection that escapes the execute promise becomes a visible inline error', async () => {
// Pre-create preparation runs before the in-function try/catch.
vi.mocked(prepareRequestForCreate).mockRejectedValue(new Error('prepare exploded'))
const creationId = runBackgroundWorktreeCreation(makeRequest())
await vi.waitFor(() => {
expect(store.pendingWorktreeCreations[creationId]).toMatchObject({
status: 'error',
error: 'prepare exploded'
})
})
expect(toast.error).not.toHaveBeenCalled()
expect(store.removePendingWorktreeCreation).not.toHaveBeenCalled()
expect(console.error).toHaveBeenCalledWith(
'worktree create: unhandled failure',
creationId,
expect.any(Error)
)
})
it('backstop: a rejection after leaving the panel is announced with a toast', async () => {
store.activeView = 'tasks'
vi.mocked(prepareRequestForCreate).mockRejectedValue(new Error('prepare exploded'))
const creationId = runBackgroundWorktreeCreation(makeRequest())
// The pending surface is revealed synchronously; move away before the
// rejected preparation reaches the fire-and-forget backstop.
store.activeView = 'tasks'
await vi.waitFor(() => {
expect(toast.error).toHaveBeenCalledWith('prepare exploded')
})
expect(store.pendingWorktreeCreations[creationId]).toMatchObject({ status: 'error' })
})
})
+27 -3
View File
@@ -1,3 +1,4 @@
import { toast } from 'sonner'
import { useAppStore } from '@/store'
import {
findPendingLinkedWorkItemCreationId,
@@ -11,11 +12,34 @@ import {
getWorktreeCreationIndeterminate
} from '@/lib/worktree-creation-flow-startup'
import { retryStructuredWorktreeLaunch } from '@/lib/worktree-creation-structured-recovery'
import {
formatWorkspaceCreateError,
getWorkspaceCreateErrorToastMessage
} from '@/lib/workspace-create-error-format'
type ContinueBackgroundWorktreeCreationOptions = {
revealCreationSurface?: boolean
}
// Why: nothing awaits these creations, so an escaped rejection would otherwise
// strand the pending entry — and the creation surface — with no error shown.
function startWorktreeCreation(creationId: string, request: WorktreeCreationRequest): void {
executeWorktreeCreation(creationId, request).catch((error: unknown) => {
console.error('worktree create: unhandled failure', creationId, error)
const store = useAppStore.getState()
if (!store.pendingWorktreeCreations[creationId]) {
return
}
const message = getWorkspaceCreateErrorToastMessage(formatWorkspaceCreateError(error))
store.updatePendingWorktreeCreation(creationId, { status: 'error', error: message })
// Why: the panel renders this error inline while its surface is visible;
// only announce it separately after the user has navigated away.
if (!(store.activeView === 'terminal' && store.activePendingCreationId === creationId)) {
toast.error(message)
}
})
}
function revealPendingCreation(
creationId: string,
request: WorktreeCreationRequest,
@@ -62,7 +86,7 @@ export function runBackgroundWorktreeCreation(request: WorktreeCreationRequest):
// client over plain HTTP). createBrowserUuid falls back to getRandomValues.
const creationId = createBrowserUuid()
revealPendingCreation(creationId, request, getInitialWorktreeCreationPhase(request))
void executeWorktreeCreation(creationId, request)
startWorktreeCreation(creationId, request)
return creationId
}
@@ -101,7 +125,7 @@ export function continueBackgroundWorktreeCreation(
store.setActiveView('terminal')
store.setSidebarOpen(true)
}
void executeWorktreeCreation(creationId, request)
startWorktreeCreation(creationId, request)
return true
}
@@ -133,5 +157,5 @@ export function retryBackgroundWorktreeCreation(creationId: string): void {
)
return
}
void executeWorktreeCreation(creationId, entry.request)
startWorktreeCreation(creationId, entry.request)
}