fix(ai-vault): give Orca servers reached over SSH the 15 s all-hosts scan budget (#31139) (#31226)

All hosts gave every Orca server a 3 s scan, including the managed servers
Orca reaches through an SSH tunnel, which now serve every SSH host. A healthy
SSH host walking a large home timed out and showed up empty. The saved
environment's connectionDependency now picks the 15 s budget for tunnelled
servers; direct LAN/Tailscale servers keep 3 s.

Reported, with the proposed approach, by @quzard in #31139.
This commit is contained in:
Neil
2026-10-20 16:11:11 -07:00
committed by GitHub
parent 9693204223
commit 026c45e9e3
6 changed files with 51 additions and 16 deletions
@@ -33,12 +33,16 @@ describe('runtime AI Vault session scanner', () => {
it('lists saved runtime environments as runtime execution hosts', () => {
mocks.listEnvironments.mockReturnValue([
{ id: 'env-1', name: 'Server 1' },
{ id: 'env-2', name: 'Server 2' }
{ id: 'env-2', name: 'Server 2', connectionDependency: 'ssh-tunnel' }
])
expect(getSavedRuntimeAiVaultHostInfos('/user-data')).toEqual([
{ environmentId: 'env-1', executionHostId: 'runtime:env-1' },
{ environmentId: 'env-2', executionHostId: 'runtime:env-2' }
{
environmentId: 'env-2',
executionHostId: 'runtime:env-2',
connectionDependency: 'ssh-tunnel'
}
])
})
+5 -6
View File
@@ -21,11 +21,7 @@ import type {
import { parseAiVaultListResult } from './session-list-result-validation'
import { parseAiVaultSessionTitlesResult } from './session-title-result-validation'
import { isGatedOffWslHost } from '../wsl/wsl-host-gate'
export type RuntimeAiVaultHostInfo = {
environmentId: string
executionHostId: `runtime:${string}`
}
import type { RuntimeAiVaultHostInfo } from '../ipc/ai-vault-runtime-scan'
export type RuntimeAiVaultScanOptions = {
timeoutMs?: number
@@ -53,7 +49,10 @@ export function getSavedRuntimeAiVaultHostInfos(
.filter((environment) => !isGatedOffWslHost(environment))
.map((environment) => ({
environmentId: environment.id,
executionHostId: toRuntimeExecutionHostId(environment.id)
executionHostId: toRuntimeExecutionHostId(environment.id),
...(environment.connectionDependency
? { connectionDependency: environment.connectionDependency }
: {})
}))
}
+15 -6
View File
@@ -1,13 +1,22 @@
import type { RuntimeAiVaultHostInfo } from './ai-vault-runtime-scan'
// Per-leg bounds for the all-hosts fan-outs, so one slow host cannot hold a merge open.
export const AI_VAULT_ALL_HOST_TIMEOUT_MS = {
runtimeScan: 3_000,
// Why: a remote home with many agent roots routinely needs seconds to walk,
// stat and parse. The old shared 3s bound emptied healthy SSH hosts in the
// all-hosts view; the relay gets a real scan budget and the whole leg (relay
// attempt plus any legacy crawl) stays bounded.
sshScanRelay: 15_000,
sshScan: 20_000,
// A search reads an index rather than walking a home, but it shares the relay
// with the scans, so it gets the relay budget rather than one of its own.
// all-hosts view; a server reached over an SSH tunnel walks the same home
// plus the tunnel's latency, so it gets a real scan budget.
sshTunneledRuntimeScan: 15_000,
// A search reads an index rather than walking a home, but it shares the
// connection with the scans, so it gets the SSH scan budget.
search: 15_000
} as const
export function allHostsRuntimeScanTimeoutMs(
hostInfo: Pick<RuntimeAiVaultHostInfo, 'connectionDependency'>
): number {
return hostInfo.connectionDependency === 'ssh-tunnel'
? AI_VAULT_ALL_HOST_TIMEOUT_MS.sshTunneledRuntimeScan
: AI_VAULT_ALL_HOST_TIMEOUT_MS.runtimeScan
}
+2
View File
@@ -12,6 +12,8 @@ import { aiVaultScanIssueResult } from '../ai-vault/session-list-results'
export type RuntimeAiVaultHostInfo = {
environmentId: string
executionHostId: `runtime:${string}`
/** The saved environment's own record of how it is reached; picks the all-hosts scan budget. */
connectionDependency?: 'ssh-tunnel'
}
export type RuntimeAiVaultScanOptions = {
+21
View File
@@ -96,6 +96,27 @@ describe('listAiVaultSessions host routing', () => {
)
})
it('gives servers reached over an SSH tunnel the longer all-hosts scan budget', async () => {
registerAiVaultHandlers({
getActiveRuntimeAiVaultHostInfos: () => [
{ environmentId: 'lan-server', executionHostId: 'runtime:lan-server' },
{
environmentId: 'ssh-server',
executionHostId: 'runtime:ssh-server',
connectionDependency: 'ssh-tunnel'
}
],
scanRuntimeAiVaultSessions: mocks.scanRuntimeAiVaultSessions
})
await _internals.listAiVaultSessions({ executionHostScope: 'all', force: true })
const timeoutFor = (environmentId: string): unknown =>
mocks.scanRuntimeAiVaultSessions.mock.calls.find(([id]) => id === environmentId)?.[2]
expect(timeoutFor('lan-server')).toEqual({ timeoutMs: 3_000 })
expect(timeoutFor('ssh-server')).toEqual({ timeoutMs: 15_000 })
})
it('keeps direct runtime host scans on the normal runtime timeout', async () => {
registerAiVaultHandlers({
getActiveRuntimeAiVaultHostInfos: () => [],
+2 -2
View File
@@ -59,7 +59,7 @@ import {
OPENS_EVERY_STRUCTURED_CHAT,
projectStructuredAiVaultSessions
} from '../ai-vault/structured-session-ownership'
import { AI_VAULT_ALL_HOST_TIMEOUT_MS } from './ai-vault-all-host-timeouts'
import { allHostsRuntimeScanTimeoutMs } from './ai-vault-all-host-timeouts'
import { registerAiVaultScanProgressBroadcast } from './ai-vault-scan-progress-broadcast'
type AiVaultHandlerOptions = AiVaultSessionSources &
@@ -156,7 +156,7 @@ async function scanAiVaultSessionsByHostScope(
listArgs: args ? { ...args, afterFingerprint: undefined } : undefined,
options: {
signal,
timeoutMs: AI_VAULT_ALL_HOST_TIMEOUT_MS.runtimeScan
timeoutMs: allHostsRuntimeScanTimeoutMs(hostInfo)
}
})
})