stablyai/orca · error

SSH relay providers were not ready for target "${targetId}".

Error message

SSH relay providers were not ready for target "${targetId}".

What it means

Thrown by waitForRuntimeSshProviders when the full SSH_PROVIDER_READY_TIMEOUT_MS (10s, polled every 100ms) elapses without both getSshGitProvider(targetId) and getSshFilesystemProvider(targetId) becoming truthy. The relay accepted the connection and connectRegisteredSshTarget returned 'connected', but the provider-registration side never published both providers, so the runtime cannot route git or filesystem operations over the target. The runtime-owned target is then removed to avoid orphaning it.

Source

Thrown at src/main/ephemeral-vm-runtime-ssh.ts:70

export async function removeRuntimeOwnedSshTarget(targetId: string | undefined): Promise<void> {
  if (!targetId) {
    return
  }
  await removeRegisteredSshTarget(targetId)
}

async function waitForRuntimeSshProviders(targetId: string, signal?: AbortSignal): Promise<void> {
  const startedAt = Date.now()
  while (Date.now() - startedAt < SSH_PROVIDER_READY_TIMEOUT_MS) {
    if (signal?.aborted) {
      throw new Error(`SSH provider wait aborted for target "${targetId}".`)
    }
    if (getSshGitProvider(targetId) && getSshFilesystemProvider(targetId)) {
      return
    }
    await new Promise((resolve) => setTimeout(resolve, SSH_PROVIDER_READY_INTERVAL_MS))
  }
  throw new Error(`SSH relay providers were not ready for target "${targetId}".`)
}

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Check that the SSH relay process is alive and that its handler registration path actually ran for this targetId (look for registerSshGitProvider / registerSshFilesystemProvider calls in logs).
  2. Reconnect the SSH target from the UI — a fresh connect attempt re-runs the registration handshake and usually clears a one-off race.
  3. If provider bootstrap is legitimately slow on this host, confirm whether SSH_PROVIDER_READY_TIMEOUT_MS is being exceeded because of system load (fix the load) rather than bumping the budget.
  4. Verify the SSH runtime build matches the host: a host that does not send the provider-ready frames (older firmware / relay version) will never satisfy the wait.

Example fix

// before
await connectRuntimeOwnedSshTarget({ runtimeId, connection })

// after: retry once on 'providers not ready', surface a reconnect prompt if it persists
try {
  await connectRuntimeOwnedSshTarget({ runtimeId, connection })
} catch (error) {
  if (error instanceof Error && error.message.includes('were not ready')) {
    await disconnectRuntimeOwnedSshTarget(targetId)
    throw new Error('SSH relay providers did not register in time. Reconnect the SSH target.')
  }
  throw error
}
Defensive patterns

Strategy: retry

Validate before calling

import { getSshGitProvider, getSshFilesystemProvider } from '../providers/ssh-git-dispatch'
import { getSshFilesystemProvider as getFs } from '../providers/ssh-filesystem-dispatch'

// Pre-check before relying on a connectionId: both providers must be live.
function sshProvidersReady(connectionId: string): boolean {
  return Boolean(getSshGitProvider(connectionId) && getFs(connectionId))
}

Type guard

function isSshProvidersNotReady(error: unknown): boolean {
  return error instanceof Error && error.message.startsWith('SSH relay providers were not ready for target ')
}

Try / catch

async function connectWithRetry(args, attempts = 2): Promise<RuntimeOwnedSshConnectionResult> {
  for (let i = 0; i < attempts; i++) {
    try { return await connectRuntimeOwnedSshTarget(args) }
    catch (error) {
      if (isSshProvidersNotReady(error) && i < attempts - 1) {
        await disconnectRuntimeOwnedSshTarget(undefined) // best-effort
        await new Promise((r) => setTimeout(r, 500))
        continue
      }
      throw error
    }
  }
  throw new Error('unreachable')
}

Prevention

When it happens

Trigger: connectRuntimeOwnedSshTarget connects the SSH target successfully, but the relay/handlers that should call registerSshGitProvider / registerSshFilesystemProvider for targetId are delayed, dropped, or never fire (handler not registered, IPC race, relay crash after connect). After 10s of polling, the loop at ephemeral-vm-runtime-ssh.ts:61 exits and throws at :70.

Common situations: SSH handlers are not registered (getSshConnectionStore returned a store but the provider dispatch wiring is incomplete in the running build); the relay process died after the connect handshake but before provider registration; high system load / slow first exec makes provider bootstrap exceed 10s; a version skew between client and SSH host where the host never sends the frames that trigger registration.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/a06961f93b56724c. Report an issue: GitHub.