jackwener/OpenCLI · error · CommandExecutionError
MiniMax music returned invalid hexadecimal audio
Error message
MiniMax music returned invalid hexadecimal audio
What it means
decodeAudioHex validates the hex payload before decoding: it must be non-empty, have even length (each byte is two hex digits), and contain only hex characters. Any violation throws, since Buffer.from(value, 'hex') would otherwise silently produce truncated or garbage bytes.
Solutions
- Print the first/last few characters and the length of data.audio to spot truncation or a '0x' prefix/base64 content
- If the value is a URL, use requireAudioUrl/download instead of decodeAudioHex
- If it looks like base64, decode with Buffer.from(value, 'base64') instead
- Increase any HTTP body size limits in your HTTP client so large hex payloads are not truncated
Example fix
// before: decoding whatever arrives const bytes = decodeAudioHex(data.audio, expectedBytes, format); // after: detect base64/hex form first const isHex = data.audio.length % 2 === 0 && /^[0-9a-f]+$/i.test(data.audio); const bytes = isHex ? decodeAudioHex(data.audio, expectedBytes, format) : decodeAudioBase64(data.audio, expectedBytes, format);
Defensive patterns
Strategy: validation
Validate before calling
function looksLikeHex(v) {
return typeof v === 'string' && v.length > 0 && v.length % 2 === 0 && /^[0-9a-f]+$/i.test(v);
}
if (!looksLikeHex(payload?.data?.audio)) {
// choose URL or base64 path, or inspect raw payload
} Type guard
function isHexString(v) {
return typeof v === 'string' && v.length % 2 === 0 && /^[0-9a-f]+$/i.test(v);
} Try / catch
try {
const bytes = decodeAudioHex(value, expectedBytes, format);
} catch (e) {
if (/invalid hexadecimal/.test(e.message)) {
if (/^[A-Za-z0-9+/=]+$/.test(value)) {
// looks like base64 — decode accordingly
} else {
console.error('audio value head:', value.slice(0, 32), 'len:', value.length);
}
} else throw e;
} Prevention
- Raise HTTP client max body size to avoid truncation of large hex payloads
- Strip whitespace/'0x' prefixes before decoding
- Detect payload encoding (hex vs base64 vs URL) before dispatching
- Never pass audio strings through lossy shell/editor pipelines
When it happens
Trigger: data.audio hex string is empty; it has odd length (a character lost to truncation or encoding corruption); it contains non-hex characters (whitespace, '0x' prefix, base64 characters, or a URL pasted into the hex path).
Common situations: HTTP client truncating large response bodies; calling the hex decoder with a URL value from a URL-returning endpoint; copying audio strings through a shell or editor that strips characters; MiniMax returning base64 instead of hex after an API change.
Related errors
- MiniMax music returned data.audio that is not a URL
- MiniMax music audio size mismatch
- MiniMax music reported completion without data.audio
- MiniMax music response is missing integer data.status
- MiniMax music returned a non-HTTPS audio URL
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/0fbd7944bb3477aa.
Report an issue: GitHub.
Appendix: source
Thrown at clis/minimax/utils.js:150
return extraInfo.music_size;
}
export function requireAudioUrl(value) {
let url;
try {
url = new URL(value);
} catch {
throw new CommandExecutionError('MiniMax music returned data.audio that is not a URL');
}
if (url.protocol !== 'https:') {
throw new CommandExecutionError(`MiniMax music returned a non-HTTPS audio URL (${url.protocol})`);
}
return url.toString();
}
export function decodeAudioHex(value, expectedBytes, format) {
if (value.length === 0 || value.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(value)) {
throw new CommandExecutionError('MiniMax music returned invalid hexadecimal audio');
}
const bytes = Buffer.from(value, 'hex');
if (expectedBytes != null && bytes.length !== expectedBytes) {
throw new CommandExecutionError(`MiniMax music audio size mismatch (expected ${expectedBytes} bytes, got ${bytes.length})`);
}
if (format === 'wav' && (bytes.length < 12 || bytes.subarray(0, 4).toString('ascii') !== 'RIFF' || bytes.subarray(8, 12).toString('ascii') !== 'WAVE')) {
throw new CommandExecutionError('MiniMax music returned bytes that are not a WAV file');
}
if (format === 'mp3' && !isMp3(bytes)) {
throw new CommandExecutionError('MiniMax music returned bytes that are not an MP3 file');
}
return bytes;
}
function isMp3(bytes) {
return bytes.length >= 3 && bytes.subarray(0, 3).toString('ascii') === 'ID3'
|| bytes.length >= 2 && bytes[0] === 0xff && (bytes[1] & 0xe0) === 0xe0;
}View on GitHub (pinned to 49907e53dc)