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
- Pass expectedCredentialKind explicitly on every connectMobileRelayForPairing call and ensure it matches the credential string.
- If the host already installed the credential, switch to the resume flow with the resume token.
- 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
- Always pass expectedCredentialKind explicitly on every connectMobileRelayForPairing call.
- Track credential provenance alongside the token so the kind is unambiguous.
- Flip to the resume flow as soon as the host reports install complete.
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
- relay credential resolved as an unexpected credential kind
- mobile relay pairing recovery pending
- stale mobile relay pairing journal
- relay_outer_${code}
- E2EE device authentication rejected
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/3c2c39a1379085f9.
Report an issue: GitHub.