stablyai/orca · error

relay credential resolved as an unexpected credential kind

Error message

relay credential resolved as an unexpected credential kind

What it means

Thrown in connectMobileRelayForPairing's acceptRelayHello when the cell hello succeeded (ok:true) but parsed.data.credentialKind does not equal args.expectedCredentialKind ?? 'invite'. The cell resolved the credential as a different kind than the client expects (default invite).

Source

Thrown at mobile/src/transport/mobile-relay-physical-client.ts:135

  function acceptRelayHello(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 !== (args.expectedCredentialKind ?? 'invite')) {
      throw new Error('relay credential resolved as an unexpected credential kind')
    }
    outerReady = true
    log('info', 'Relay: cell accepted credential', 'Starting E2EE handshake')
    channel.start()
  }

  function fail(error: Error): void {
    if (closed) {
      return
    }
    closed = true
    if (intentionallyClosed) {
      log('info', 'Relay: pairing socket closed', cellHost)
    } else {
      log('warn', 'Relay: pairing socket closed', pairingRelayErrorDetail(error))
    }
    channel.dispose()
    rejectAuthenticated(error)

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Pass expectedCredentialKind explicitly on every connectMobileRelayForPairing call and ensure it matches the credential string.
  2. If the host already installed the credential, switch to the resume flow with the resume token.
  3. Regenerate a credential of the correct kind if the stored one was promoted.

Example fix

// before
connectMobileRelayForPairing({
  relay,
  credential: resumeToken,
  // expectedCredentialKind omitted -> defaults to 'invite', token is resume
  ...
})
// after
connectMobileRelayForPairing({
  relay,
  credential: resumeToken,
  expectedCredentialKind: 'resume',
  ...
})
Defensive patterns

Strategy: validation

Validate before calling

function assertCredentialKindOption(credentialKind: 'invite' | 'resume' | undefined, credential: string): 'invite' | 'resume' {
  const kind = credentialKind ?? 'invite'
  if (!credential) throw new Error('missing credential for kind ' + kind)
  return kind
}

Try / catch

try {
  const client = connectMobileRelayForPairing(args)
} catch (error) {
  if (error instanceof Error && error.message === 'relay credential resolved as an unexpected credential kind') {
    // host may have installed; switch to resume flow with the resume token
  }
}

Prevention

When it happens

Trigger: The caller passed an invite token but set expectedCredentialKind:'resume', or passed a resume token while leaving the default 'invite'; the cell promoted an invite to resume after install completed.

Common situations: State-machine bug mislabeling the credential kind; pairing already completed so the invite is interpreted as resume; the caller forgot to set expectedCredentialKind on a resume dial.

Related errors


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