From c5f48b5cd36df344be9f9ece2e4e5c43aafd29e6 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Mon, 26 Oct 2026 20:39:47 -0700 Subject: [PATCH] fix(terminal): scope account restart metadata to its execution host (#34214) --- .../lib/agent-account-restart-startup.test.ts | 63 ++++++++++++++++++- .../src/lib/agent-account-restart-startup.ts | 12 +++- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/lib/agent-account-restart-startup.test.ts b/src/renderer/src/lib/agent-account-restart-startup.test.ts index e7d4cadc475..419370fbd40 100644 --- a/src/renderer/src/lib/agent-account-restart-startup.test.ts +++ b/src/renderer/src/lib/agent-account-restart-startup.test.ts @@ -10,7 +10,12 @@ import { type ProjectExecutionRuntimeResolution } from '../../../shared/project-execution-runtime' import type * as localPreflightContext from '@/lib/local-preflight-context' -import { makeTerminalTab, makeWorktree } from '@/store/slices/worktrees-slice-test-fixtures' +import { resolveAgentResumeLaunchTarget } from '@/lib/agent-resume-launch-target' +import { + makeTerminalTab, + makeWorktree, + TEST_REPO +} from '@/store/slices/worktrees-slice-test-fixtures' import { buildPaneAccountRestartStartup, resolveAgentAccountRestartResumeStartup, @@ -19,6 +24,8 @@ import { let projectRuntimeContext: ProjectExecutionRuntimeResolution | undefined +vi.mock('@/lib/agent-resume-launch-target', { spy: true }) + vi.mock('@/lib/local-preflight-context', async (importOriginal) => ({ ...(await importOriginal()), getLocalProjectExecutionRuntimeContext: () => projectRuntimeContext @@ -306,6 +313,60 @@ describe('buildPaneAccountRestartStartup for Claude', () => { ) }) + it.each([ + ['workspace folder', undefined, "cd '/recorded/session' && "], + ['saved tab folder', '/recorded/session', ''] + ] as const)('uses the selected host and respects the %s on resume', (_, startupCwd, prefix) => { + const original = useAppStore.getState() + try { + seedAgentStatus({ + agentType: 'claude', + state: 'done', + providerSession: { key: 'session_id', id: SESSION_ID, cwd: '/recorded/session' } + }) + useAppStore.setState({ + repos: [ + TEST_REPO, + { ...TEST_REPO, executionHostId: 'runtime:remote-host', connectionId: 'selected-ssh' } + ], + worktreesByRepo: { + [TEST_REPO.id]: [ + makeWorktree({ + id: WORKTREE_ID, + repoId: TEST_REPO.id, + path: '/recorded/session', + hostId: 'local' + }), + makeWorktree({ + id: WORKTREE_ID, + repoId: TEST_REPO.id, + path: '/remote/workspace', + hostId: 'runtime:remote-host' + }) + ] + }, + detectedWorktreesByRepo: {}, + activeWorktreeId: WORKTREE_ID, + activeWorkspaceExecutionHostId: 'runtime:remote-host', + tabsByWorktree: { + [WORKTREE_ID]: [makeTerminalTab({ id: TAB_ID, worktreeId: WORKTREE_ID, startupCwd })] + } + }) + + expect(buildPaneAccountRestartStartup('claude', pane)?.command).toBe( + `${prefix}claude '--resume' '${SESSION_ID}'` + ) + expect(resolveAgentResumeLaunchTarget).toHaveBeenLastCalledWith( + expect.objectContaining({ + executionHostId: 'runtime:remote-host', + connectionId: 'selected-ssh' + }) + ) + } finally { + useAppStore.setState(original) + } + }) + it('reports no resume when the Claude pane has no session id', () => { seedAgentStatus({ agentType: 'claude', state: 'done' }) diff --git a/src/renderer/src/lib/agent-account-restart-startup.ts b/src/renderer/src/lib/agent-account-restart-startup.ts index b51bd66f2ff..80a709e1332 100644 --- a/src/renderer/src/lib/agent-account-restart-startup.ts +++ b/src/renderer/src/lib/agent-account-restart-startup.ts @@ -8,6 +8,7 @@ import { import { getExecutionHostIdForWorktree } from '@/lib/worktree-runtime-owner' import { runtimeEnvironmentIdUnlessTwin } from '@/lib/resolve-owner' import { getLocalProjectExecutionRuntimeContext } from '@/lib/local-preflight-context' +import { repoMatchesHostIdentity } from '@/store/slices/repo-host-identity' import { makePaneKey } from '../../../shared/stable-pane-id' import { normalizeAgentProviderSession } from '../../../shared/agent-session-resume' import { @@ -103,7 +104,10 @@ export function resolveAgentAccountRestartResumeStartup( // Both restart executors spawn in the tab's start folder. launchCwd: state.tabsByWorktree[args.worktreeId]?.find((tab) => tab.id === args.tabId)?.startupCwd ?? - state.getKnownWorktreeById(args.worktreeId)?.path + state.getKnownWorktreeById( + args.worktreeId, + getExecutionHostIdForWorktree(state, args.worktreeId) + )?.path }) if (!startupPlan) { return null @@ -158,9 +162,11 @@ function resolveAccountRestartLaunchTarget( args: AccountRestartTarget, projectRuntime: ProjectExecutionRuntimeResolution | undefined ): AgentResumeLaunchTarget { - const worktree = state.getKnownWorktreeById(args.worktreeId) - const repo = worktree ? state.repos.find((entry) => entry.id === worktree.repoId) : null const executionHostId = getExecutionHostIdForWorktree(state, args.worktreeId) + const worktree = state.getKnownWorktreeById(args.worktreeId, executionHostId) + const repo = worktree + ? state.repos.find((entry) => repoMatchesHostIdentity(entry, worktree.repoId, executionHostId)) + : null return resolveAgentResumeLaunchTarget({ projectRuntime, connectionId: repo?.connectionId,