fix(ssh): narrow prune capability error mapping

This commit is contained in:
Brennan Benson
2026-07-16 11:11:53 -07:00
parent fe4bc46bd6
commit b933fb2e4a
4 changed files with 46 additions and 15 deletions
+6 -5
View File
@@ -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 () => {
+2 -7
View File
@@ -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 {
@@ -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)
+13 -3
View File
@@ -673,9 +673,19 @@ export class SshGitProvider implements IGitProvider {
}
async pruneWorktrees(repoPath: string): Promise<void> {
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(