stablyai/orca · error

Failed to terminate SSH host sessions: ${shutdownFailures.jo

Error message

Failed to terminate SSH host sessions: ${shutdownFailures.join('; ')}

What it means

Thrown after attempting to shut down remote SSH PTY sessions when one or more provider.shutdown() calls rejected with a reason that is not an 'SSH pty not found' error. Non-not-found failures indicate the remote shell may still be alive in the grace window, so the code intentionally keeps the lease/session intact to let the user retry. The aggregated relayPtyId:reason pairs are joined so the caller can see which specific sessions failed and why.

Source

Thrown at src/main/ipc/ssh.ts:1357

            )
          )
        : []
      const shutdownFailures: string[] = []
      for (const [index, result] of shutdownResults.entries()) {
        const { appPtyId, relayPtyId } = ptyIds[index]
        if (result.status !== 'fulfilled' && !isSshPtyNotFoundError(result.reason)) {
          shutdownFailures.push(
            `${relayPtyId}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
          )
          continue
        }
        clearProviderPtyState(appPtyId)
        deletePtyOwnership(appPtyId)
        persistedStore!.markSshRemotePtyLease(args.targetId, relayPtyId, 'terminated')
      }
      if (shutdownFailures.length > 0) {
        // Why: a failed relay shutdown can leave the remote process alive in the grace window; keep the lease/session so the user can retry.
        throw new Error(`Failed to terminate SSH host sessions: ${shutdownFailures.join('; ')}`)
      }
      await teardownSshTargetTransport(args.targetId, (session) => session.disposeAndPersist())
    })
  })

  async function doResetRelay(targetId: string, target: SshTarget): Promise<void> {
    const inFlightConnect = connectInFlight.get(targetId)
    if (inFlightConnect) {
      try {
        // Why: resetting activeSessions mid-deploy would dispose the session doConnect will use.
        await inFlightConnect.promise
      } catch {
        // The reset can still recover a stale remote relay after a failed connect.
      }
    }

    rotateSshProviderAuthority(targetId)
    const session = activeSessions.get(targetId)

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Retry the terminate request; transient relay errors often clear on the second attempt.
  2. Inspect the per-pty reasons in the message to identify which remote host/session is stuck, then address that shell directly (e.g. log in and kill the PID).
  3. If the relay itself is unhealthy, run 'ssh:resetRelay' first to rebuild the transport, then retry terminate.
  4. As a last resort for truly wedged remote shells, accept the lease stays and let the remote OS reap the process.
Defensive patterns

Strategy: retry

Try / catch

for (let attempt = 0; attempt < 2; attempt++) {
  try {
    await ipcRenderer.invoke('ssh:terminateSessions', { targetId })
    break
  } catch (e) {
    if (attempt < 1 && /Failed to terminate SSH host sessions/.test((e as Error).message)) continue
    showTerminationFailures((e as Error).message)
    throw e
  }
}

Prevention

When it happens

Trigger: Promise.allSettled over ptyIds where at least one provider.shutdown(immediate:true, keepHistory:false) rejected with a transport error, timeout, permission error, or unexpected relay response. isSshPtyNotFoundError filtered out only the benign already-gone case.

Common situations: Relay under load returns a transient error. Remote shell is wedged and ignores the shutdown signal within the timeout. Permission/quota issue on the remote host blocks process termination. Partial network partition where some shutdowns succeed and others fail.

Related errors


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