stablyai/orca · error · RelayOuterError

relay_outer_${code}

Error message

relay_outer_${code}

What it means

RelayOuterError is thrown (message relay_outer_${code}) when the cell hello has ok:false with a code in [4000,4999]. This is the relay cell explicitly rejecting the connect credential supplied in the relay-auth frame.

Source

Thrown at mobile/src/transport/mobile-relay-e2ee-link.ts:121

    this.socket.onclose = (event) => this.fail(new RelayOuterError(event.code || 1006))
  }

  private acceptHello(raw: unknown): void {
    if (typeof raw !== 'string') {
      throw new Error('expected plaintext relay hello')
    }
    let value: unknown
    try {
      value = JSON.parse(raw)
    } catch {
      throw new Error('invalid relay hello JSON')
    }
    const parsed = RelayPhoneHelloSchema.safeParse(value)
    if (!parsed.success) {
      throw new Error('invalid relay hello')
    }
    if (!parsed.data.ok) {
      throw new RelayOuterError(parsed.data.code)
    }
    if (parsed.data.credentialKind !== this.options.expectedCredentialKind) {
      throw new Error('relay credential resolved as an unexpected credential kind')
    }
    this.outerReady = true
    this.options.onHello?.(parsed.data)
    this.channel.start()
  }

  private fail(error: Error): void {
    if (this.closed) {
      return
    }
    this.closed = true
    this.channel.dispose()
    this.options.onError(error)
    this.socket.close()
  }

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Decode the 4xxx code against the relay protocol and map it to user-facing guidance (expired vs revoked vs unknown).
  2. For expired/used invites, generate a fresh invite from the desktop host and restart the connect.
  3. For resume failures, fall back to a fresh pair.
  4. Compare the current time against leaseExpiresAt / resumeExpiresAt from a prior successful hello to catch clock skew.
Defensive patterns

Strategy: try-catch

Type guard

import { RelayOuterError } from './mobile-relay-e2ee-link'
function isRelayOuterError(error: unknown): error is RelayOuterError {
  return error instanceof RelayOuterError
}

Try / catch

import { RelayOuterError } from './mobile-relay-e2ee-link'
try {
  const link = new MobileRelayE2eeLink(options)
} catch (error) {
  if (error instanceof RelayOuterError) {
    switch (error.code) {
      case 4101: // example: expired invite
        promptRegenerateInvite()
        break
      default:
        showRelayRejection(error.code)
    }
  }
}

Prevention

When it happens

Trigger: The cell received an invite or resume credential it refuses: expired invite, already-consumed invite, unknown/revoked resume token, credential version superseded after a host reassignment.

Common situations: Invite expired before the user completed pairing; resume token invalidated after host rotation; clock skew making a lease look expired; replaying a credential from a completed/abandoned attempt.

Related errors


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