{"record":{"id":"5611024539c260a4","repo":"stablyai/orca","slug":"ssh-provider-wait-aborted-for-target-targetid","errorCode":null,"errorMessage":"SSH provider wait aborted for target \"${targetId}\".","messagePattern":"SSH provider wait aborted for target \"(.+?)\"\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/main/ephemeral-vm-runtime-ssh.ts","lineNumber":63,"sourceCode":"export async function disconnectRuntimeOwnedSshTarget(targetId: string | undefined): Promise<void> {\n  if (!targetId) {\n    return\n  }\n  await disconnectRegisteredSshTarget(targetId)\n}\n\nexport 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":45,"sourceCodeEnd":72,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/ephemeral-vm-runtime-ssh.ts#L45-L72","documentation":"Thrown by waitForRuntimeSshProviders when the caller-supplied AbortSignal becomes aborted during the up-to-10s polling loop that waits for both the SSH git provider and SSH filesystem provider to register for a freshly connected target. The connect attempt in connectRuntimeOwnedSshTarget is being cancelled mid-flight, so the runtime-owned SSH target is removed (cleanup) and the abort error is rethrown. It is a cooperative cancellation signal, not a transport failure.","triggerScenarios":"connectRuntimeOwnedSshTarget is called with an AbortSignal (e.g. an ephemeral VM recipe is cancelled, the user closes the launching dialog, or a higher-level operation races and wins). During the <=10s window where getSshGitProvider(targetId) and getSshFilesystemProvider(targetId) are still undefined, signal.aborted flips true and the next loop iteration throws at ephemeral-vm-runtime-ssh.ts:63.","commonSituations":"User cancels an ephemeral VM / SSH recipe launch while the relay is still bringing up providers; a UI timeout on the launch surface aborts the connect; concurrent connect attempts to the same runtime where one wins and the other is aborted; tests that pass a short AbortSignal and do not await provider registration.","solutions":["If the abort is expected (user cancelled), surface a 'Launch cancelled' state in the UI and stop retrying — do not treat this as a transient transport error.","If the abort is coming from an overly tight caller timeout, lengthen or remove the caller's AbortSignal so the 10s SSH_PROVIDER_READY_TIMEOUT_MS budget can do its job.","If providers are genuinely slow to register and you need more time, that is a different error (1041) — do not paper over it by aborting; investigate relay registration latency instead.","Ensure only one connect attempt owns a given runtimeId at a time so a winning attempt does not abort a still-valid in-flight one."],"exampleFix":"// before\nconst ctrl = new AbortController()\nsetTimeout(() => ctrl.abort(), 2_000) // too tight for relay bring-up\nawait connectRuntimeOwnedSshTarget({ runtimeId, connection, signal: ctrl.signal })\n\n// after: let the provider-wait budget run; abort only on real user cancel\nawait connectRuntimeOwnedSshTarget({ runtimeId, connection, signal: userCancelSignal })","handlingStrategy":"try-catch","validationCode":"import { getSshGitProvider, getSshFilesystemProvider } from './providers/ssh-dispatch'\n\n// Before connect, there is nothing to validate (target not created yet).\n// Instead, scope the abort to real user cancellation only.\nconst userCancel = new AbortController()\ndocument.getElementById('cancel-launch').onclick = () => userCancel.abort()\n// Do NOT add a short setTimeout(...).abort() here — the 10s provider-wait budget is intentional.","typeGuard":"function isSshProviderWaitAborted(error: unknown): boolean {\n  return error instanceof Error && error.message.startsWith('SSH provider wait aborted for target ')\n}","tryCatchPattern":"try {\n  await connectRuntimeOwnedSshTarget({ runtimeId, connection, signal: userCancel.signal })\n} catch (error) {\n  if (isSshProviderWaitAborted(error)) {\n    setLaunchState('cancelled')  // user-cancelled, not a transport fault\n    return\n  }\n  throw error\n}","preventionTips":["Only abort connectRuntimeOwnedSshTarget from a real user-cancel controller, never a short timeout.","Serialise connect attempts per runtimeId so a winning attempt does not abort a still-valid in-flight one.","Distinguish abort (1040) from provider-not-ready timeout (1041) in the UI — they need different responses."],"tags":["ssh","abort","ephemeral-vm","provider-registration","cancellation"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}