{"record":{"id":"50b0f28303874bba","repo":"jackwener/OpenCLI","slug":"minimax-music-returned-bytes-that-are-not-a-wav-fi","errorCode":null,"errorMessage":"MiniMax music returned bytes that are not a WAV file","messagePattern":"MiniMax music returned bytes that are not a WAV file","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/minimax/utils.js","lineNumber":157,"sourceCode":"    } catch {\n        throw new CommandExecutionError('MiniMax music returned data.audio that is not a URL');\n    }\n    if (url.protocol !== 'https:') {\n        throw new CommandExecutionError(`MiniMax music returned a non-HTTPS audio URL (${url.protocol})`);\n    }\n    return url.toString();\n}\n\nexport function decodeAudioHex(value, expectedBytes, format) {\n    if (value.length === 0 || value.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(value)) {\n        throw new CommandExecutionError('MiniMax music returned invalid hexadecimal audio');\n    }\n    const bytes = Buffer.from(value, 'hex');\n    if (expectedBytes != null && bytes.length !== expectedBytes) {\n        throw new CommandExecutionError(`MiniMax music audio size mismatch (expected ${expectedBytes} bytes, got ${bytes.length})`);\n    }\n    if (format === 'wav' && (bytes.length < 12 || bytes.subarray(0, 4).toString('ascii') !== 'RIFF' || bytes.subarray(8, 12).toString('ascii') !== 'WAVE')) {\n        throw new CommandExecutionError('MiniMax music returned bytes that are not a WAV file');\n    }\n    if (format === 'mp3' && !isMp3(bytes)) {\n        throw new CommandExecutionError('MiniMax music returned bytes that are not an MP3 file');\n    }\n    return bytes;\n}\n\nfunction isMp3(bytes) {\n    return bytes.length >= 3 && bytes.subarray(0, 3).toString('ascii') === 'ID3'\n        || bytes.length >= 2 && bytes[0] === 0xff && (bytes[1] & 0xe0) === 0xe0;\n}\n\nexport function resolveOutputDir(value) {\n    const raw = String(value ?? '').trim();\n    if (!raw) return path.join(os.homedir(), 'Music', 'minimax');\n    if (raw === '~') return os.homedir();\n    if (raw.startsWith('~/')) return path.join(os.homedir(), raw.slice(2));\n    if (raw.startsWith('~')) throw new ArgumentError(`Unsupported home-directory path: ${raw}`);","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/minimax/utils.js#L139-L175","documentation":"decodeAudioHex decodes the hex-encoded audio payload MiniMax returns for music generation and verifies it matches the requested container format. When audioFormat is 'wav', the library throws this CommandExecutionError if the decoded bytes lack a valid RIFF/WAVE header (must start with 'RIFF' and contain 'WAVE' at offset 8, minimum 12 bytes). This guards against the MiniMax API silently returning wrong-format or corrupted audio.","triggerScenarios":"Calling decodeAudioHex (via the MiniMax music flow) with format='wav' while the decoded hex payload does not begin with 'RIFF'....'WAVE' — e.g. the API returned MP3 bytes despite a WAV request, or a truncated payload shorter than 12 bytes.","commonSituations":"MiniMax changes or misconfigures its audio_setting.format handling server-side; a proxy or cached response substitutes a different codec; the hex string was truncated or mangled in transit so the header check fails.","solutions":["Verify the request body sent audio_setting.format='wav' and re-run the generation","Decode a few bytes of data.audio and check whether they actually look like MP3 (ID3 or 0xFFEx sync) — if so, request format='mp3' instead","Retry the music_generation call; if it reproduces, capture the response (trace_id, base_resp) and report to MiniMax support","Check the hex string for corruption upstream (odd length, non-hex chars would already fail earlier; here suspect truncation before decode)"],"exampleFix":"// before: requesting WAV but API returned MP3 bytes\nconst bytes = decodeAudioHex(data.audio, expectedBytes, 'wav'); // throws\n// after: request the format the API actually honors\nconst bytes = decodeAudioHex(data.audio, expectedBytes, 'mp3');\nfs.writeFileSync('out.mp3', bytes);","handlingStrategy":"validation","validationCode":"function looksLikeWav(hex) {\n    if (!/^[0-9a-f]+$/i.test(hex) || hex.length % 2 !== 0) return false;\n    const bytes = Buffer.from(hex, 'hex');\n    return bytes.length >= 12\n        && bytes.subarray(0, 4).toString('ascii') === 'RIFF'\n        && bytes.subarray(8, 12).toString('ascii') === 'WAVE';\n}\n// call before decodeAudioHex(..., 'wav'): if (!looksLikeWav(data.audio)) request mp3 or retry","typeGuard":"function isWavBuffer(bytes) {\n    return Buffer.isBuffer(bytes) && bytes.length >= 12\n        && bytes.subarray(0, 4).toString('ascii') === 'RIFF'\n        && bytes.subarray(8, 12).toString('ascii') === 'WAVE';\n}","tryCatchPattern":"try {\n    const bytes = decodeAudioHex(data.audio, expectedBytes, 'wav');\n} catch (e) {\n    if (String(e.message).includes('not a WAV file')) {\n        // fall back: detect actual format and save accordingly\n        const mp3 = decodeAudioHex(data.audio, expectedBytes, 'mp3');\n        fs.writeFileSync(out + '.mp3', mp3);\n    } else throw e;\n}","preventionTips":["Always send audio_setting.format explicitly and consistently in buildRequest","Sniff the first bytes of decoded audio before saving and derive the file extension from them","Log base_resp/trace_id when format mismatches occur to support MiniMax reports","Pin/monitor API behavior changes; add an integration test asserting WAV magic bytes"],"tags":["minimax","audio","wav","format-validation"],"backgroundTag":"unexpected-audio-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}