{"record":{"id":"a06961f93b56724c","repo":"stablyai/orca","slug":"ssh-relay-providers-were-not-ready-for-target-t","errorCode":null,"errorMessage":"SSH relay providers were not ready for target \"${targetId}\".","messagePattern":"SSH relay providers were not ready for target \"(.+?)\"\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/main/ephemeral-vm-runtime-ssh.ts","lineNumber":70,"sourceCode":"export async function removeRuntimeOwnedSshTarget(targetId: string | undefined): Promise<void> {\n  if (!targetId) {\n    return\n  }\n  await removeRegisteredSshTarget(targetId)\n}\n\nasync function waitForRuntimeSshProviders(targetId: string, signal?: AbortSignal): Promise<void> {\n  const startedAt = Date.now()\n  while (Date.now() - startedAt < SSH_PROVIDER_READY_TIMEOUT_MS) {\n    if (signal?.aborted) {\n      throw new Error(`SSH provider wait aborted for target \"${targetId}\".`)\n    }\n    if (getSshGitProvider(targetId) && getSshFilesystemProvider(targetId)) {\n      return\n    }\n    await new Promise((resolve) => setTimeout(resolve, SSH_PROVIDER_READY_INTERVAL_MS))\n  }\n  throw new Error(`SSH relay providers were not ready for target \"${targetId}\".`)\n}\n","sourceCodeStart":52,"sourceCodeEnd":72,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/ephemeral-vm-runtime-ssh.ts#L52-L72","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","Reconnect the SSH target from the UI — a fresh connect attempt re-runs the registration handshake and usually clears a one-off race.","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.","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."],"exampleFix":"// before\nawait connectRuntimeOwnedSshTarget({ runtimeId, connection })\n\n// after: retry once on 'providers not ready', surface a reconnect prompt if it persists\ntry {\n  await connectRuntimeOwnedSshTarget({ runtimeId, connection })\n} catch (error) {\n  if (error instanceof Error && error.message.includes('were not ready')) {\n    await disconnectRuntimeOwnedSshTarget(targetId)\n    throw new Error('SSH relay providers did not register in time. Reconnect the SSH target.')\n  }\n  throw error\n}","handlingStrategy":"retry","validationCode":"import { getSshGitProvider, getSshFilesystemProvider } from '../providers/ssh-git-dispatch'\nimport { getSshFilesystemProvider as getFs } from '../providers/ssh-filesystem-dispatch'\n\n// Pre-check before relying on a connectionId: both providers must be live.\nfunction sshProvidersReady(connectionId: string): boolean {\n  return Boolean(getSshGitProvider(connectionId) && getFs(connectionId))\n}","typeGuard":"function isSshProvidersNotReady(error: unknown): boolean {\n  return error instanceof Error && error.message.startsWith('SSH relay providers were not ready for target ')\n}","tryCatchPattern":"async function connectWithRetry(args, attempts = 2): Promise<RuntimeOwnedSshConnectionResult> {\n  for (let i = 0; i < attempts; i++) {\n    try { return await connectRuntimeOwnedSshTarget(args) }\n    catch (error) {\n      if (isSshProvidersNotReady(error) && i < attempts - 1) {\n        await disconnectRuntimeOwnedSshTarget(undefined) // best-effort\n        await new Promise((r) => setTimeout(r, 500))\n        continue\n      }\n      throw error\n    }\n  }\n  throw new Error('unreachable')\n}","preventionTips":["After a connect, verify sshProvidersReady(targetId) before queueing git/filesystem work that assumes the relay is up.","Prompt an explicit reconnect from the UI on this error rather than silently retrying in a loop.","Investigate relay registration latency on the host if this fires repeatedly — it usually points to a host/relay version mismatch or load issue, not a normal condition."],"tags":["ssh","ephemeral-vm","timeout","provider-registration","relay"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}