tinyhumansai/openhuman · error · Error
[transport:manager] tunnel profile missing sessionToken or p
Error message
[transport:manager] tunnel profile missing sessionToken or pairingToken
What it means
Tunnel authentication needs either an established sessionToken or the one-time pairingToken (sessionToken ?? pairingToken). With neither, TunnelTransport cannot authenticate to the backend socket relay, so the manager aborts before connecting.
Source
Thrown at app/src/services/transport/TransportManager.ts:119
throw new Error(`[transport:manager] unknown profile kind: ${kind}`);
}
/**
* Race LAN (with 2 s timeout) against Tunnel.
* Whichever responds to `openhuman.ping` first wins.
* If LAN wins but later fails, caller should call reset() to re-race.
*/
private async raceLanAndTunnel(): Promise<CoreTransport> {
const { rpcUrl, channelId, corePubkey, sessionToken, pairingToken, devicePrivkey } =
this.profile;
if (!channelId || !corePubkey) {
throw new Error('[transport:manager] tunnel profile missing channelId or corePubkey');
}
const tunnelToken = sessionToken ?? pairingToken;
if (!tunnelToken) {
throw new Error('[transport:manager] tunnel profile missing sessionToken or pairingToken');
}
const tunnelTransport = new TunnelTransport(
this.backendSocketUrl,
channelId,
corePubkey,
tunnelToken,
devicePrivkey,
sessionToken ? 'session' : 'pairing'
);
if (!rpcUrl) {
// No LAN URL — tunnel only.
log('[transport:manager] → TunnelTransport (no LAN URL)');
return tunnelTransport;
}
const lanTransport = new LanHttpTransport(rpcUrl, LAN_RACE_TIMEOUT_MS);View on GitHub (pinned to a221052e0d)
Solutions
- Re-pair the device to obtain a fresh pairingToken/sessionToken pair
- If the sessionToken exists but is rejected later, route through re-auth rather than reusing the profile
- Persist the session token atomically with pairing completion so the two cannot diverge
Example fix
// before
const tm = createTransportManager(profile, opts);
// after
if (profile.kind === 'tunnel' && !(profile.sessionToken ?? profile.pairingToken)) {
await rePairDevice(profile.id); // yields fresh tokens
}
const tm = createTransportManager(profile, opts); Defensive patterns
Strategy: validation
Validate before calling
function assertTunnelAuthPresent(p: ConnectionProfile): void {
if (p.kind === 'tunnel' && !(p.sessionToken ?? p.pairingToken)) {
throw new Error(`profile ${p.id}: no session or pairing token — re-pair the device`);
}
} Prevention
- Remember pairing tokens are single-use — never persist a profile with a consumed token and no session token
- Persist the session token atomically with pairing completion
- Route expired sessions through re-auth instead of reusing stale profiles
When it happens
Trigger: A tunnel profile whose pairingToken was already consumed by a previous successful pairing (they are single-use) and whose sessionToken was never persisted — e.g. the client crashed between server-side pairing completion and storing the session token.
Common situations: Crash between pairing-token exchange and session persistence; session token expired or cleared; restored backup carrying stale tokens.
Related errors
- [transport:manager] tunnel profile missing channelId or core
- [tunnel] no device keypair
- [transport:cloud] HTTP ${response.status}: ${text || respons
- [transport:local] HTTP ${response.status}: ${text || respons
- [transport:manager] all transports failed to connect
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/32c1f60dd95229ca.
Report an issue: GitHub.