From b933fb2e4a2c7fdc6fd4e0bf5485672f5f0732fa Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:11:53 -0700 Subject: [PATCH] fix(ssh): narrow prune capability error mapping --- src/main/ipc/worktrees.test.ts | 11 ++++----- src/main/ipc/worktrees.ts | 9 ++------ src/main/providers/ssh-git-provider.test.ts | 25 +++++++++++++++++++++ src/main/providers/ssh-git-provider.ts | 16 ++++++++++--- 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/main/ipc/worktrees.test.ts b/src/main/ipc/worktrees.test.ts index 4daa4c745c4..5d45e899745 100644 --- a/src/main/ipc/worktrees.test.ts +++ b/src/main/ipc/worktrees.test.ts @@ -6095,7 +6095,7 @@ describe('registerWorktreeHandlers', () => { expect(pruneWorktreesMock).not.toHaveBeenCalled() }) - it('fails worktrees:pruneStaleRegistrations clearly when the relay lacks prune support', async () => { + it('surfaces the provider error when the relay lacks prune support', async () => { const repo = { id: 'repo-1', path: '/remote/repo', @@ -6105,15 +6105,16 @@ describe('registerWorktreeHandlers', () => { connectionId: 'ssh-1' } store.getRepos.mockReturnValue([repo]) - // Why: an already-deployed relay predating git.pruneWorktrees rejects the - // RPC with "Method not found"; the error must say what to do instead. + const oldRelayError = new Error( + 'The connected SSH host is running an older Orca relay without worktree prune support. Reconnect to update it, or run `git worktree prune` there.' + ) getSshGitProviderMock.mockReturnValue({ - pruneWorktrees: vi.fn().mockRejectedValue(new Error('Method not found: git.pruneWorktrees')) + pruneWorktrees: vi.fn().mockRejectedValue(oldRelayError) }) await expect( handlers['worktrees:pruneStaleRegistrations'](null, { repoId: 'repo-1' }) - ).rejects.toThrow(/older Orca relay/) + ).rejects.toBe(oldRelayError) }) it('continues shared-host pruning and invalidates stale roots after a host fails', async () => { diff --git a/src/main/ipc/worktrees.ts b/src/main/ipc/worktrees.ts index c286694467b..79080e88766 100644 --- a/src/main/ipc/worktrees.ts +++ b/src/main/ipc/worktrees.ts @@ -2050,13 +2050,8 @@ export function registerWorktreeHandlers( await pruneWorktrees(repo.path, getLocalProjectWorktreeGitOptions(store, repo)) } } catch (error) { - // Why: an already-deployed relay predating git.pruneWorktrees - // rejects with "Method not found"; surface what to do instead. - firstError ??= /method not found/i.test(error instanceof Error ? error.message : '') - ? new Error( - 'The connected SSH host is running an older Orca relay without worktree prune support. Reconnect to update it, or run `git worktree prune` there.' - ) - : error + // Why: shared project ids can span hosts; attempt every host before surfacing a failure. + firstError ??= error } } } finally { diff --git a/src/main/providers/ssh-git-provider.test.ts b/src/main/providers/ssh-git-provider.test.ts index eb8383a3f60..ad953a25ec2 100644 --- a/src/main/providers/ssh-git-provider.test.ts +++ b/src/main/providers/ssh-git-provider.test.ts @@ -1268,6 +1268,31 @@ describe('SshGitProvider', () => { }) }) + it('pruneWorktrees sends the narrow prune request', async () => { + await provider.pruneWorktrees('/home/user/repo') + + expect(mux.request).toHaveBeenCalledWith('git.pruneWorktrees', { + repoPath: '/home/user/repo' + }) + }) + + it('pruneWorktrees maps old relays to the reconnect message', async () => { + mux.request.mockRejectedValueOnce( + Object.assign(new Error('Method not found: git.pruneWorktrees'), { code: -32601 }) + ) + + await expect(provider.pruneWorktrees('/home/user/repo')).rejects.toThrow( + 'The connected SSH host is running an older Orca relay without worktree prune support. Reconnect to update it, or run `git worktree prune` there.' + ) + }) + + it('pruneWorktrees rethrows non-method-not-found errors', async () => { + const error = new Error('remote prune failed') + mux.request.mockRejectedValueOnce(error) + + await expect(provider.pruneWorktrees('/home/user/repo')).rejects.toBe(error) + }) + it('worktreeIsClean sends git.worktreeIsClean request', async () => { const cleanResult = { clean: false, stdout: '?? scratch.txt\n' } mux.request.mockResolvedValue(cleanResult) diff --git a/src/main/providers/ssh-git-provider.ts b/src/main/providers/ssh-git-provider.ts index 98f7241c314..86d4222f3aa 100644 --- a/src/main/providers/ssh-git-provider.ts +++ b/src/main/providers/ssh-git-provider.ts @@ -673,9 +673,19 @@ export class SshGitProvider implements IGitProvider { } async pruneWorktrees(repoPath: string): Promise { - await this.runWithDiffDedupeClear(async () => { - await this.mux.request('git.pruneWorktrees', { repoPath }) - }) + try { + await this.runWithDiffDedupeClear(async () => { + await this.mux.request('git.pruneWorktrees', { repoPath }) + }) + } catch (error) { + if (isJsonRpcMethodNotFoundError(error)) { + // Why: older relays predate the narrow prune RPC; tell the user how to recover. + throw new Error( + 'The connected SSH host is running an older Orca relay without worktree prune support. Reconnect to update it, or run `git worktree prune` there.' + ) + } + throw error + } } async worktreeIsClean(