colinhacks/zod · error · Error
Invalid hex string length
Error message
Invalid hex string length
What it means
Thrown by `util.hexToUint8Array` when the input string has an odd length after stripping a leading `0x` prefix (guard at util.ts:964). Hex bytes are pairs of characters, so an odd-length string cannot be parsed into bytes — the function rejects rather than guess padding. It's used internally by hash decoders and any consumer calling the util directly.
Source
Thrown at packages/zod/src/v4/core/util.ts:965
binaryString += String.fromCharCode(bytes[i]);
}
return btoa(binaryString);
}
export function base64urlToUint8Array(base64url: string): InstanceType<typeof Uint8Array> {
const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
const padding = "=".repeat((4 - (base64.length % 4)) % 4);
return base64ToUint8Array(base64 + padding);
}
export function uint8ArrayToBase64url(bytes: Uint8Array): string {
return uint8ArrayToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
export function hexToUint8Array(hex: string): InstanceType<typeof Uint8Array> {
const cleanHex = hex.replace(/^0x/, "");
if (cleanHex.length % 2 !== 0) {
throw new Error("Invalid hex string length");
}
const bytes = new Uint8Array(cleanHex.length / 2);
for (let i = 0; i < cleanHex.length; i += 2) {
bytes[i / 2] = Number.parseInt(cleanHex.slice(i, i + 2), 16);
}
return bytes;
}
export function uint8ArrayToHex(bytes: Uint8Array): string {
return Array.from(bytes)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
// instanceof
export abstract class Class {
constructor(..._args: any[]) {}
}View on GitHub (pinned to 912f0f51b0)
Solutions
- Pre-validate length: `if (hex.replace(/^0x/, '').length % 2 !== 0) throw new Error('hex must be even-length')`.
- Pad with a leading zero if semantically correct: `hex.padStart(targetLen, '0')`.
- Regenerate the source string from `util.uint8ArrayToHex(bytes)` to guarantee even length.
Example fix
// before const bytes = util.hexToUint8Array(rawHash); // throws if odd-length // after const clean = rawHash.replace(/^0x/, ''); const bytes = util.hexToUint8Array(clean.length % 2 ? '0' + clean : clean);
Defensive patterns
Strategy: validation
Validate before calling
function toBytes(hex: string) {
const clean = hex.replace(/^0x/, '');
if (clean.length % 2 !== 0) throw new Error('hex string must have even length');
return util.hexToUint8Array(clean);
} Type guard
function isEvenHex(s: string): boolean {
return /^[0-9a-fA-F]*$/.test(s.replace(/^0x/, '')) &&
s.replace(/^0x/, '').length % 2 === 0;
} Try / catch
try { bytes = util.hexToUint8Array(raw); }
catch (e) {
if (e instanceof Error && /hex string length/i.test(e.message)) {
bytes = util.hexToUint8Array(raw.replace(/^0x/, '').padStart(/* even */, '0'));
} else throw e;
} Prevention
- Normalize hex strings before decoding: strip `0x`, lowercase, pad to even length.
- Validate input shape at API boundaries before passing to hash decoders.
When it happens
Trigger: Calling `hexToUint8Array('abc')` (3 chars), `'0x1f3'` (3 after prefix), or any hex string with an odd character count. Also reached indirectly via `z.string().format('hash')` or custom code paths that decode hex hashes.
Common situations: Truncated or partially-trimmed hash strings from logs; copy-paste losing a character; concatenating hex fragments that joined to odd length; user input not pre-validated before being passed to a hash decoder.
Related errors
- Unrecognized hash format: ${format}
- Invalid UUID version: "${def.version}"
- Invalid discriminated union option at index "${def.options.i
- Invalid discriminated union option at index "${def.options.i
- Duplicate discriminator value "${String(v)}"
AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03).
Data as JSON: /data/errors/d3a2888ede6d9a5b.json.
Report an issue: GitHub.