From ec04827b37bc38f5be0ec54a5a2dab00ca3f3d52 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 24 Jul 2026 00:36:50 -0700 Subject: [PATCH] test(session): guard against workspace-reopen tab fork-bomb (STA-1111) (#10057) * test(session): guard against workspace-reopen tab fork-bomb (STA-1111) Relates to STA-1111 (already fixed on main by #6945). The runaway-tab-on-reopen bug was fixed by #6945 (activeOrQueuedResumeClaimsProviderSession dedup guard) hours before the ticket was filed. Verified: disabling that guard makes tab count climb 1->2->3->4 across reopens; restoring it holds at 1. This adds a revert-sensitive regression test covering the created-agent and sleeping-resume reopen paths so it cannot regress. Test plan: 2 new tests pass; fail if the #6945 guard is removed. * test(session): isolate automatic resume replay coverage * test(session): assert resumed tab identity stays stable --- ...worktree-reactivation-tab-forkbomb.test.ts | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/renderer/src/lib/worktree-reactivation-tab-forkbomb.test.ts diff --git a/src/renderer/src/lib/worktree-reactivation-tab-forkbomb.test.ts b/src/renderer/src/lib/worktree-reactivation-tab-forkbomb.test.ts new file mode 100644 index 000000000..a0c24f245 --- /dev/null +++ b/src/renderer/src/lib/worktree-reactivation-tab-forkbomb.test.ts @@ -0,0 +1,101 @@ +import path from 'node:path' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { useAppStore, type AppState } from '@/store' +import { activateAndRevealWorktree } from './worktree-activation' +import { makeCreatedAgentWorktree as makeWorktree } from '@/lib/worktree-activation-created-agent-test-state' + +const initialAppStoreState = useAppStore.getState() + +function baseState(worktree: ReturnType): Partial { + return { + repos: [ + { + id: 'repo-1', + path: path.join(path.sep, 'workspace', 'repo'), + displayName: 'repo', + badgeColor: '#000000', + addedAt: 0 + } + ], + worktreesByRepo: { 'repo-1': [worktree] }, + activeRepoId: 'repo-1', + activeView: 'terminal', + tabsByWorktree: {}, + unifiedTabsByWorktree: {}, + groupsByWorktree: {}, + layoutByWorktree: {}, + activeGroupIdByWorktree: {}, + openFiles: [], + browserTabsByWorktree: {}, + activeFileIdByWorktree: {}, + activeBrowserTabIdByWorktree: {}, + activeTabTypeByWorktree: {}, + activeTabIdByWorktree: {}, + tabBarOrderByWorktree: {}, + pendingStartupByTabId: {}, + automaticAgentResumeClaimsByTabId: {}, + agentStatusByPaneKey: {}, + sleepingAgentSessionsByPaneKey: {}, + settings: { + agentCmdOverrides: {}, + setupScriptLaunchMode: 'new-tab' + } as unknown as ReturnType['settings'], + markWorktreeVisited: vi.fn(), + recordWorktreeVisit: vi.fn(), + refreshGitHubForWorktreeIfStale: vi.fn(), + revealWorktreeInSidebar: vi.fn() + } +} + +afterEach(() => { + useAppStore.setState(initialAppStoreState, true) +}) + +describe('STA-1111 worktree reopen does not fork-bomb tabs', () => { + it('re-captured sleeping codex session resumes once, not once per reopen', () => { + const worktree = { ...makeWorktree(), createdWithAgent: undefined } + useAppStore.setState(baseState(worktree)) + const providerSession = { key: 'session_id' as const, id: 'codex-session-1' } + let resumedTabId: string | undefined + + for (let reopen = 0; reopen < 4; reopen++) { + const paneKey = `slept-pane-${reopen}:0` + useAppStore.setState((s) => ({ + sleepingAgentSessionsByPaneKey: { + ...s.sleepingAgentSessionsByPaneKey, + [paneKey]: { + paneKey, + tabId: `slept-pane-${reopen}`, + worktreeId: worktree.id, + agent: 'codex', + providerSession, + prompt: 'resume prior task', + state: 'working', + origin: 'live', + capturedAt: 1000 + reopen, + updatedAt: 1000 + reopen, + terminalTitle: 'Codex' + } + } + })) + + activateAndRevealWorktree(worktree.id) + const state = useAppStore.getState() + const tabs = state.tabsByWorktree[worktree.id] ?? [] + + expect(tabs).toHaveLength(1) + resumedTabId ??= tabs[0]!.id + expect(tabs[0]!.id).toBe(resumedTabId) + expect(state.automaticAgentResumeClaimsByTabId[tabs[0]!.id]?.providerSession).toEqual( + providerSession + ) + expect(state.sleepingAgentSessionsByPaneKey[paneKey]).toBeUndefined() + + if (reopen === 0) { + expect(state.consumeTabStartupCommand(tabs[0]!.id)?.resumeProviderSession).toEqual( + providerSession + ) + } + } + }) +})