stablyai/orca · error · Error

Runtime environment pairing changed; refresh and try again

Error message

Runtime environment pairing changed; refresh and try again

What it means

Thrown at the start of a remote runtime subscription when args.expectedEnvironmentPairingRevision is provided and does not match the environment's current pairingRevision (falling back to createdAt). This is an optimistic-concurrency guard: pairing revisions change whenever the environment is paired/re-paired to a host, so a mismatch means the client's view is stale.

Source

Thrown at src/main/ipc/runtime-environments.ts:111

      }
    ): Promise<{ subscriptionId: string; requestId: string }> => {
      const subscriptionId =
        typeof args.subscriptionId === 'string' && args.subscriptionId.length > 0
          ? args.subscriptionId
          : randomUUID()
      if (remoteRuntimeSubscriptions.has(subscriptionId)) {
        throw new Error('Runtime environment subscription id already exists')
      }
      const environment = resolveEnvironment(getUserDataPath(), args.selector)
      if (isRuntimeEnvironmentManuallyDisconnected(environment.id)) {
        throw new Error('runtime_manually_disconnected')
      }
      const pairingRevision = environment.pairingRevision ?? environment.createdAt
      if (
        args.expectedEnvironmentPairingRevision !== undefined &&
        pairingRevision !== args.expectedEnvironmentPairingRevision
      ) {
        throw new Error('Runtime environment pairing changed; refresh and try again')
      }
      const transportGeneration = getRuntimeEnvironmentTransportGeneration(environment.id)
      const transportIsCurrent = (): boolean =>
        getRuntimeEnvironmentTransportGeneration(environment.id) === transportGeneration
      const sender = event.sender
      const ownerWebContentsId = sender.id
      let senderDestroyed = sender.isDestroyed()
      let subscription: RemoteRuntimeSubscription | null = null
      let destroyedListenerAttached = false
      const removeDestroyedListener = (): void => {
        if (!destroyedListenerAttached) {
          return
        }
        destroyedListenerAttached = false
        sender.removeListener('destroyed', closeSubscription)
      }
      const closeSubscription = (): void => {
        senderDestroyed = true

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Catch the error, re-resolve the environment to refresh pairingRevision, and retry the subscription once.
  2. Do not cache pairingRevision beyond the current view; fetch it fresh before subscribing.
  3. Subscribe to runtime environment change events to invalidate the cached revision proactively.
  4. If retries keep failing, prompt the user to re-pair the environment.

Example fix

// before
ipc.invoke('runtimeEnvironments:subscribe', { selector, expectedEnvironmentPairingRevision: cached })

// after
try {
  await ipc.invoke('runtimeEnvironments:subscribe', { selector, expectedEnvironmentPairingRevision: cached })
} catch (e) {
  if (!/pairing changed/.test(e.message)) throw e
  const fresh = await ipc.invoke('runtimeEnvironments:resolve', { selector })
  await ipc.invoke('runtimeEnvironments:subscribe', {
    selector,
    expectedEnvironmentPairingRevision: fresh.pairingRevision ?? fresh.createdAt,
  })
}
Defensive patterns

Strategy: retry

Validate before calling

const env = await ipc.invoke('runtimeEnvironments:resolve', { selector })
const rev = env.pairingRevision ?? env.createdAt
// pass rev as expectedEnvironmentPairingRevision; refresh env before each attempt
await ipc.invoke('runtimeEnvironments:subscribe', { selector, expectedEnvironmentPairingRevision: rev })

Type guard

function hasPairingRevision(env: { pairingRevision?: unknown; createdAt: unknown }): boolean {
  return typeof env.pairingRevision !== 'undefined' || typeof env.createdAt !== 'undefined'
}

Try / catch

async function subscribe(selector: string, rev: unknown) {
  try {
    return await ipc.invoke('runtimeEnvironments:subscribe', { selector, expectedEnvironmentPairingRevision: rev })
  } catch (e) {
    if (!/pairing changed/.test((e as Error).message)) throw e
    const fresh = await ipc.invoke('runtimeEnvironments:resolve', { selector })
    return ipc.invoke('runtimeEnvironments:subscribe', {
      selector,
      expectedEnvironmentPairingRevision: fresh.pairingRevision ?? fresh.createdAt,
    })
  }
}

Prevention

When it happens

Trigger: Opening a remote runtime stream with a cached pairingRevision while the environment was re-paired (e.g. reconnection, re-keying, or server-side rotation) in another window or session. The handler also pre-checks manual-disconnect and duplicate subscription id before reaching this guard.

Common situations: Multiple windows where one re-pairs the environment; a long-open client whose cached revision aged out; automated re-pair flows during a session; restored session state pointing at an old revision.

Related errors


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