fix(ssh): stop a failed worktree scan from publishing authoritative emptiness (#17833)

* fix(ssh): keep an unreadable worktree catalog from authorizing teardown

#14004: the relay's worktree-list fallback caught every failure and returned
`[]`, so `SshGitProvider.listWorktrees` resolved as a success with an empty
list. Downstream reconciliation treats a resolved listing as authoritative,
which reaches `teardownMissingWorktreeTerminalsBestEffort` and the
unregistered-worktree removal paths — a data-loss path from a failed scan.

- relay: the `-z`-unsupported fallback lane propagates its failure instead of
  swallowing it to `[]`.
- provider: an empty or malformed `git.listWorktrees` response is refused as
  `WorktreeCatalogUnavailableError`. A Git repo always lists its own checkout,
  so a zero-row listing can only be a scan that never answered — this is the
  mixed-version guard against relays that still swallow.
- `listRepoWorktrees`: an unreachable SSH host reports unavailable instead of
  an empty catalog.

#12661: `ssh:terminateSessions` now returns `{ terminated, unverifiable }`, so
an offline sweep that only tore down local transport cannot be mistaken for a
remote kill. The Manage-hosts toast warns instead of claiming success.

* chore(i18n): register the unreachable-terminal terminate message
This commit is contained in:
Neil
2026-09-01 22:05:30 -07:00
committed by GitHub
parent bed9734a9d
commit 058e618bb4
14 changed files with 389 additions and 35 deletions
+3 -2
View File
@@ -9,7 +9,8 @@ import type {
SshTarget,
SshTargetAddResult,
SshTargetCreateInput,
SshTargetUpdateInput
SshTargetUpdateInput,
SshTerminateSessionsResult
} from '../../shared/ssh-types'
import type { FilesystemPathFlavor } from '../../shared/filesystem-entry-types'
@@ -25,7 +26,7 @@ export type SshApi = {
resolveConfigHost: (args: { alias: string }) => Promise<SshConfigHostResolution | null>
connect: (args: { targetId: string }) => Promise<SshConnectionState | null>
disconnect: (args: { targetId: string }) => Promise<void>
terminateSessions: (args: { targetId: string }) => Promise<void>
terminateSessions: (args: { targetId: string }) => Promise<SshTerminateSessionsResult>
resetRelay: (args: { targetId: string }) => Promise<void>
getState: (args: { targetId: string }) => Promise<SshConnectionState | null>
needsPassphrasePrompt: (args: { targetId: string }) => Promise<boolean>