can1357/oh-my-pi · error · Error
Room key must be ${ROOM_KEY_BYTES} bytes, got ${raw.byteLeng
Error message
Room key must be ${ROOM_KEY_BYTES} bytes, got ${raw.byteLength} What it means
importRoomKey wraps a raw symmetric AES-GCM key for the collaboration room. The library enforces that the raw key bytes are exactly ROOM_KEY_BYTES (32 bytes); anything else is rejected before reaching WebCrypto so a mistyped or truncated secret fails loudly instead of producing a key that cannot decrypt host frames. This guards the room-key wire format produced by parseCollabLink and generateRoomKey.
Source
Thrown at packages/coding-agent/src/collab/crypto.ts:29
const IV_LENGTH = 12;
const TEXT_ENCODER = new TextEncoder();
const TEXT_DECODER = new TextDecoder();
export function generateRoomKey(): Uint8Array {
const key = new Uint8Array(ROOM_KEY_BYTES);
crypto.getRandomValues(key);
return key;
}
export function generateWriteToken(): Uint8Array {
const token = new Uint8Array(WRITE_TOKEN_BYTES);
crypto.getRandomValues(token);
return token;
}
export function importRoomKey(raw: Uint8Array): Promise<CryptoKey> {
if (raw.byteLength !== ROOM_KEY_BYTES) {
throw new Error(`Room key must be ${ROOM_KEY_BYTES} bytes, got ${raw.byteLength}`);
}
return crypto.subtle.importKey("raw", asStrict(raw), AES_ALGORITHM, false, ["encrypt", "decrypt"]);
}
export async function seal(key: CryptoKey, frame: CollabFrame): Promise<Uint8Array> {
const iv = new Uint8Array(IV_LENGTH);
crypto.getRandomValues(iv);
const plaintext = TEXT_ENCODER.encode(JSON.stringify(frame));
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: AES_ALGORITHM, iv }, key, plaintext));
const out = new Uint8Array(IV_LENGTH + ciphertext.byteLength);
out.set(iv, 0);
out.set(ciphertext, IV_LENGTH);
return out;
}
/** Inverse of {@link seal}. Throws on auth failure or malformed input. */
export async function open(key: CryptoKey, data: Uint8Array): Promise<CollabFrame> {
if (data.byteLength <= IV_LENGTH) {View on GitHub (pinned to 9690622007)
Solutions
- Decode the key strictly from the link's base64url key fragment via parseCollabLink, which validates the 32-byte (or 48-byte with token) length before you call importRoomKey.
- Check raw.byteLength === ROOM_KEY_BYTES before calling and regenerate/re-obtain the key if it differs.
- If the key came from hex or a password, derive/re-encode it to exactly 32 bytes (e.g. Buffer.from(hex, 'hex') must yield 32 bytes) before importing.
- For custom keys, use crypto.getRandomValues(new Uint8Array(ROOM_KEY_BYTES)) as generateRoomKey does.
Example fix
// before
const key = await importRoomKey(Buffer.from(secretHex, "hex")); // 16 or 64 bytes for many hex inputs
// after
const raw = Buffer.from(secretB64url, "base64url");
if (raw.byteLength !== ROOM_KEY_BYTES) throw new Error("bad room key length");
const key = await importRoomKey(new Uint8Array(raw)); Defensive patterns
Strategy: validation
Validate before calling
if (raw.byteLength !== ROOM_KEY_BYTES) throw new Error(`expected ${ROOM_KEY_BYTES}-byte room key, got ${raw.byteLength}`);
const key = await importRoomKey(raw); Type guard
function isValidRoomKey(raw: Uint8Array): boolean {
return raw instanceof Uint8Array && raw.byteLength === ROOM_KEY_BYTES;
} Prevention
- Always obtain the key via parseCollabLink or generateRoomKey, never by hand-decoding strings.
- Use base64url (not hex or base64) when round-tripping key bytes.
- Assert key length in tests that construct keys manually.
When it happens
Trigger: Calling importRoomKey(raw) with a Uint8Array whose byteLength !== ROOM_KEY_BYTES: a key decoded from a truncated/corrupted collab link, a hand-rolled hex string decoded to 16/64 bytes, a key padded or stripped by base64 vs base64url confusion, or a key sliced to the wrong subarray bounds.
Common situations: A guest pastes a collab link that was mangled by a terminal or chat client (truncated base64url); a developer constructs the key from a hex-encoded secret instead of the base64url fragment; an older client produced 16-byte keys before the format moved to 32; tests hardcode short dummy keys.
Related errors
- Room key must be ${KEY_LENGTH} bytes, got ${raw.byteLength}
- ${parsed.error}
- Sealed frame too short
- ${normalized.error}
- Unsupported language '{value}'. Supported: {}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/0e53e65451a8b3f1.
Report an issue: GitHub.