fix(runtime): stop one unreachable relay from freezing every workspace as active (STA-517) (#14649)

* fix(runtime): stop one unreachable relay from freezing every workspace as active

The worktree.ps liveness refresh is the only thing that retires an exited PTY,
and mobile renders "active" straight off the summary it produces. Its aggregate
inventory ran every provider through Promise.all with no per-provider deadline,
so a single SSH relay that rejected — or simply did not answer inside the 3s
budget, since a relay list runs to the mux's own 30s default — cost the runtime
the whole inventory. Nothing was ever proven dead, so every retained pane kept
reporting hasHostSidebarActivity/liveTerminalCount, and the SSH workspaces stayed
"active" on mobile for as long as the connection stayed unreachable.

Settle each SSH provider independently and forward the caller's deadline, so
local and healthy relays are still reconciled. A provider that does not answer is
unknown, not empty: the runtime's existing hasPty rescue keeps its panes. A local
failure still fails the aggregate, matching pty:listSessions.

The restored-orchestration-authority sweep now runs after that rescue, so a pane
the controller still vouches for keeps its handle instead of losing it to a
listing that merely omitted it.

STA-517

* test(runtime): assert the provider scope, not the exact arity, of inventory calls

These assertions exist to prove which provider scope the inventory asked for.
Forwarding the caller's deadline added a second argument, which broke them on
arity alone. Match the scope argument and require a numeric deadline beside it,
so the intent is preserved and the budget is covered too.

* test(runtime): type the inventory mock's scope parameter

A bare `async () =>` mock types mock.calls as an empty tuple, so reading the
scope argument off it fails typecheck. Declare the parameter the runtime
actually passes.
This commit is contained in:
Brennan Benson
2026-08-18 00:35:16 -07:00
committed by GitHub
parent 545b3fba08
commit eb0ec39242
6 changed files with 432 additions and 38 deletions
+15 -10
View File
@@ -1004,6 +1004,9 @@ async function referenceStatusFrameLines(
}
const TEST_WINDOW_ID = 1
// The inventory refresh forwards its own budget so a relay cannot outlive it (STA-517).
// These assertions are about which provider scope was asked, so the deadline stays loose.
const LIST_PROVIDER_DEADLINE = expect.objectContaining({ deadlineMs: expect.any(Number) })
const TEST_REPO_ID = 'repo-1'
const TEST_REPO_PATH = '/tmp/repo'
const TEST_WORKTREE_PATH = '/tmp/worktree-a'
@@ -20306,7 +20309,7 @@ describe('OrcaRuntimeService', () => {
paneKey: makePaneKey('tab-agent', HEADLESS_LEAF_ID)
})
expect(listProcesses).toHaveBeenCalledTimes(inventoryCount + 1)
expect(listProcesses).toHaveBeenLastCalledWith(null)
expect(listProcesses).toHaveBeenLastCalledWith(null, LIST_PROVIDER_DEADLINE)
expect(
(await runtime.listTerminals()).terminals.find((terminal) => terminal.ptyId === 'pty-agent')
).toMatchObject({
@@ -20316,7 +20319,7 @@ describe('OrcaRuntimeService', () => {
leafId: HEADLESS_LEAF_ID
})
expect(listProcesses).toHaveBeenCalledTimes(inventoryCount + 2)
expect(listProcesses).toHaveBeenLastCalledWith(undefined)
expect(listProcesses).toHaveBeenLastCalledWith(undefined, LIST_PROVIDER_DEADLINE)
await expect(
runtime.adoptTerminalOrphans({
worktree: `id:${TEST_WORKTREE_ID}`,
@@ -21582,7 +21585,7 @@ describe('OrcaRuntimeService', () => {
expect(getSession().sleepingAgentSessionsByPaneKey?.[workerPaneKey]).toBeUndefined()
expect(getSession().sleepingAgentSessionsByPaneKey?.[secondWorkerPaneKey]).toBeUndefined()
expect(listProcesses).toHaveBeenCalledTimes(3)
expect(listProcesses).toHaveBeenCalledWith(null)
expect(listProcesses).toHaveBeenCalledWith(null, LIST_PROVIDER_DEADLINE)
expect(getSession().tabsByWorktree[TEST_WORKTREE_ID]).toEqual([
expect.objectContaining({ id: 'legacy-worker-two', ptyId: 'pty-exited-two' })
])
@@ -21697,7 +21700,7 @@ describe('OrcaRuntimeService', () => {
await vi.advanceTimersByTimeAsync(1_000)
expect(listProcesses).toHaveBeenCalledTimes(4)
expect(listProcesses.mock.calls).toEqual([[null], [null], [null], [null]])
expect(listProcesses.mock.calls.map((call) => call[0])).toEqual([null, null, null, null])
expect(hasPty).not.toHaveBeenCalled()
expect(getSession().tabsByWorktree[TEST_WORKTREE_ID]).toEqual([
expect.objectContaining({ id: 'legacy-worker', ptyId: 'pty-inventory-unavailable' })
@@ -21888,7 +21891,7 @@ describe('OrcaRuntimeService', () => {
deferredDispatchIds: ['dispatch-missing', 'dispatch-ambiguous']
})
expect(listProcesses).toHaveBeenCalledOnce()
expect(listProcesses).toHaveBeenCalledWith(null)
expect(listProcesses).toHaveBeenCalledWith(null, LIST_PROVIDER_DEADLINE)
for (const { name, leafId } of cases.slice(0, 2)) {
expect(
getSession().sleepingAgentSessionsByPaneKey?.[`legacy-${name}:${leafId}`]
@@ -21998,7 +22001,7 @@ describe('OrcaRuntimeService', () => {
)
expect(getSession().sleepingAgentSessionsByPaneKey?.[workerPaneKey]).toBeUndefined()
expect(listProcesses).toHaveBeenCalledTimes(3)
expect(listProcesses).toHaveBeenCalledWith(null)
expect(listProcesses).toHaveBeenCalledWith(null, LIST_PROVIDER_DEADLINE)
expect(revealTerminalSession).toHaveBeenCalledWith(TEST_FOLDER_WORKSPACE_KEY, {
ptyId: 'pty-folder-legacy',
title: 'Folder worker',
@@ -22140,7 +22143,7 @@ describe('OrcaRuntimeService', () => {
expect(getWorkspaceSession).toHaveBeenCalledWith(`ssh:${connectionId}`)
expect(setWorkspaceSession).toHaveBeenCalledWith(expect.any(Object), `ssh:${connectionId}`)
expect(listProcesses).toHaveBeenCalledTimes(3)
expect(listProcesses).toHaveBeenCalledWith(connectionId)
expect(listProcesses).toHaveBeenCalledWith(connectionId, LIST_PROVIDER_DEADLINE)
expect(sshSession.tabsByWorktree[TEST_FOLDER_WORKSPACE_KEY]).toContainEqual(
expect.objectContaining({
id: 'legacy-ssh-folder-worker',
@@ -22369,7 +22372,7 @@ describe('OrcaRuntimeService', () => {
exitedDispatchIds: [],
deferredDispatchIds: []
})
expect(listProcesses).toHaveBeenLastCalledWith(connectionId)
expect(listProcesses).toHaveBeenLastCalledWith(connectionId, LIST_PROVIDER_DEADLINE)
} finally {
unregisterSshGitProvider(connectionId)
}
@@ -22455,7 +22458,9 @@ describe('OrcaRuntimeService', () => {
}
]
} as unknown as OrchestrationDb)
const listProcesses = vi.fn(async () => [
// Declares the scope parameter so mock.calls keeps it — the runtime passes a deadline
// alongside it, and a bare `async () =>` would type the call tuple as empty.
const listProcesses = vi.fn(async (_connectionId?: string | null) => [
{
id: 'pty-wsl-legacy',
incarnationId,
@@ -22506,7 +22511,7 @@ describe('OrcaRuntimeService', () => {
expect(getSession().sleepingAgentSessionsByPaneKey?.[workerPaneKey]).toBeUndefined()
expect(revealTerminalSession).toHaveBeenCalledOnce()
expect(listProcesses).toHaveBeenCalledTimes(5)
expect(listProcesses.mock.calls).toEqual([[null], [null], [null], [null], [null]])
expect(listProcesses.mock.calls.map((call) => call[0])).toEqual([null, null, null, null, null])
})
it('restores orphan pane and group topology without replacing a newer host-owned tab', async () => {