tinyhumansai/openhuman · error · Error
[tunnel] no device keypair
Error message
[tunnel] no device keypair
What it means
TunnelTransport.sendHandshake needs the device's long-term keypair to prove identity during the XChaCha20-Poly1305 handshake. This fires when connect() reached the handshake stage without a keypair — none was supplied to the constructor (the manager passes profile.devicePrivkey) or it was never generated/loaded.
Source
Thrown at app/src/services/transport/TunnelTransport.ts:157
this.clientEphemeralKeypair = null;
this.cipher = null;
this._connectPromise = null;
});
socket.on('connect_error', (err: Error) => {
logErr('[tunnel] connect_error %s', err.message);
reject(err);
this._connectPromise = null;
});
});
return this._connectPromise;
}
// -- handshake -------------------------------------------------------------
private async sendHandshake(): Promise<void> {
if (!this.deviceKeypair) throw new Error('[tunnel] no device keypair');
const corePubkey = base64urlDecode(this.corePubkeyB64);
const devicePubkeyB64 = base64urlEncode(this.deviceKeypair.publicKey);
const clientEphemeral = generateKeypair();
const clientEphemeralPubkeyB64 = base64urlEncode(clientEphemeral.publicKey);
const payload = new TextEncoder().encode(
JSON.stringify({
device_pubkey: devicePubkeyB64,
client_ephemeral_pubkey: clientEphemeralPubkeyB64,
})
);
// Seal the handshake payload to the core's public key.
const handshakeFrame = sealHandshake(corePubkey, payload);
const frameB64 = base64urlEncode(handshakeFrame);
log('[tunnel] sending sealed handshake frame_len=%d', handshakeFrame.length);View on GitHub (pinned to a221052e0d)
Solutions
- Complete device pairing so devicePrivkey is stored with the profile (keyring/security-devices flow)
- When constructing TunnelTransport manually, generate/load the device keypair first and pass it in
- Refuse to build a kind 'tunnel' profile without devicePrivkey at validation time
Example fix
// before const t = new TunnelTransport(url, channelId, corePubkey, token, undefined, 'pairing'); await t.connect(); // after const devicePrivkey = await loadOrCreateDeviceKey(); // Uint8Array from keyring const t = new TunnelTransport(url, channelId, corePubkey, token, devicePrivkey, 'pairing'); await t.connect();
Defensive patterns
Strategy: validation
Validate before calling
function tunnelProfileHasDeviceKey(p: ConnectionProfile): boolean {
return p.kind !== 'tunnel' || !!p.devicePrivkey;
} Prevention
- Complete device pairing (key generation + storage) before any tunnel profile is marked usable
- When constructing TunnelTransport directly, load-or-create the device keypair first
- Fail profile validation when devicePrivkey is absent on a tunnel profile
When it happens
Trigger: Constructing TunnelTransport from a tunnel profile lacking devicePrivkey and calling connect(); or a code path invoking sendHandshake before device key generation completed (e.g. test harness passing only token arguments).
Common situations: Tunnel profile persisted without the device private key (pairing never fully completed); tests building TunnelTransport directly with positional args and omitting the key.
Related errors
- [transport:manager] tunnel profile missing channelId or core
- [transport:manager] tunnel profile missing sessionToken or p
- [tunnel] no session cipher — handshake incomplete
- [transport:manager] all transports failed to connect
- [transport:cloud] ${method} timed out after ${this.timeoutMs
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/efd86df7c999fa33.
Report an issue: GitHub.