fix(desktop): look up v1 SSH terminals with the bootstrap pool key

resolveDesktopRemoteRoute tags v1 SSH with a registry connectionId when
the host matches. The terminal then looked up sshConnections with
backendScopeKey(connectionId, profile), but bootstrap stored the
tunnel under sshScopeKey(profile or null). The pane stayed pending.

Ignore the identity tag for v1 pool lookup.
This commit is contained in:
Adolanium
2026-08-26 13:37:17 +03:00
committed by kshitij
parent d72cbbcf5a
commit dde7ed42b3
3 changed files with 60 additions and 5 deletions
@@ -3,7 +3,8 @@ import assert from 'node:assert/strict'
import { test } from 'vitest'
import { normalizeRegistry, REGISTRY_VERSION } from './connection-registry'
import { resolveDesktopRemoteRoute } from './desktop-remote-route'
import { backendScopeKey } from './connection-registry'
import { resolveDesktopRemoteRoute, v1SshTerminalPoolKey } from './desktop-remote-route'
const tokenA = { encoding: 'plain', value: 'token-a' }
const tokenB = { encoding: 'plain', value: 'token-b' }
@@ -153,6 +154,46 @@ test('global SSH treats an omitted port as 22 and checks the primary route', ()
assert.equal(route?.connectionId, 'ssh-primary')
})
test('v1 settings SSH pool key ignores registry identity tags', () => {
const route = resolveDesktopRemoteRoute({
config: { mode: 'ssh', remote: { mode: 'ssh', host: 'box.test', user: 'hermes' } },
profile: 'worker',
registry: registry('ssh-primary', [
{ id: 'ssh-primary', kind: 'ssh', label: 'SSH primary', host: 'box.test', user: 'hermes', port: 22 }
])
})
assert.ok(route)
assert.equal(route.kind, 'ssh')
assert.equal(route.connectionId, 'ssh-primary')
assert.equal(v1SshTerminalPoolKey(route, 'worker'), '')
assert.notEqual(v1SshTerminalPoolKey(route, 'worker'), backendScopeKey(route.connectionId, 'worker'))
})
test('v1 profile SSH pool key is the profile, not conn:id::profile', () => {
const ssh = {
mode: 'ssh',
host: 'box.test',
user: 'hermes',
port: 2222,
keyPath: '/keys/a',
remoteHermesPath: '/srv/hermes',
remoteProfile: 'worker'
}
const route = resolveDesktopRemoteRoute({
config: { mode: 'local', profiles: { worker: ssh } },
profile: 'worker',
registry: registry('local', [{ id: 'worker-ssh', kind: 'ssh', label: 'Worker SSH', ...ssh }])
})
assert.ok(route)
assert.equal(route.kind, 'ssh')
assert.equal(route.connectionId, 'worker-ssh')
assert.equal(v1SshTerminalPoolKey(route, 'worker'), 'worker')
assert.notEqual(v1SshTerminalPoolKey(route, 'worker'), backendScopeKey(route.connectionId, 'worker'))
})
test('profile route omits identity when two registry entries match exactly', () => {
const block = { mode: 'remote', url: 'https://worker.test', authMode: 'token', token: tokenA }
@@ -51,6 +51,22 @@ function withConnectionId<T extends object>(route: T, connectionId?: string): T
return connectionId ? { ...route, connectionId } : route
}
/**
* Pool key used by v1 settings/profile SSH (`sshConnections`).
*
* Bootstrap stores the tunnel under sshScopeKey(profile or null).
* resolveDesktopRemoteRoute may also stamp a registry connectionId when
* the host matches a v2 row. That id is identity, not the pool key.
* Looking up backendScopeKey(connectionId, profile) misses the live tunnel.
*/
export function v1SshTerminalPoolKey(route: { source: string }, profile?: null | string): string {
if (route.source === 'profile') {
return connectionScopeKey(profile) || ''
}
return ''
}
/**
* Select one remote route with the existing precedence and freeze any exact
* registry identity before I/O. A null result means the profile resolves
+2 -4
View File
@@ -158,7 +158,7 @@ import { describeCrashReason, installCrashForensics } from './crash-forensics'
import { adoptServedDashboardToken } from './dashboard-token'
import { loadOrCreateInstallationId, sshOwnershipId } from './desktop-installation'
import { formatDesktopLogLine } from './desktop-log-line'
import { resolveDesktopRemoteRoute } from './desktop-remote-route'
import { resolveDesktopRemoteRoute, v1SshTerminalPoolKey } from './desktop-remote-route'
import {
buildPosixCleanupScript,
buildWindowsCleanupScript,
@@ -10335,9 +10335,7 @@ function activeSshTerminalTarget(webContentsId?: number) {
return null
}
const scope = route.connectionId
? backendScopeKey(route.connectionId, profile)
: sshScopeKey(route.source === 'profile' ? profile : null)
const scope = v1SshTerminalPoolKey(route, profile)
const state = sshConnections.get(scope)