jackwener/OpenCLI · error · CommandExecutionError

MiniMax music returned bytes that are not a WAV file

Error message

MiniMax music returned bytes that are not a WAV file

What it means

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.

Source

Thrown at clis/minimax/utils.js:157

    } 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;
}

export function resolveOutputDir(value) {
    const raw = String(value ?? '').trim();
    if (!raw) return path.join(os.homedir(), 'Music', 'minimax');
    if (raw === '~') return os.homedir();
    if (raw.startsWith('~/')) return path.join(os.homedir(), raw.slice(2));
    if (raw.startsWith('~')) throw new ArgumentError(`Unsupported home-directory path: ${raw}`);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Verify the request body sent audio_setting.format='wav' and re-run the generation
  2. 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
  3. Retry the music_generation call; if it reproduces, capture the response (trace_id, base_resp) and report to MiniMax support
  4. Check the hex string for corruption upstream (odd length, non-hex chars would already fail earlier; here suspect truncation before decode)

Example fix

// before: requesting WAV but API returned MP3 bytes
const bytes = decodeAudioHex(data.audio, expectedBytes, 'wav'); // throws
// after: request the format the API actually honors
const bytes = decodeAudioHex(data.audio, expectedBytes, 'mp3');
fs.writeFileSync('out.mp3', bytes);
Defensive patterns

Strategy: validation

Validate before calling

function looksLikeWav(hex) {
    if (!/^[0-9a-f]+$/i.test(hex) || hex.length % 2 !== 0) return false;
    const bytes = Buffer.from(hex, 'hex');
    return bytes.length >= 12
        && bytes.subarray(0, 4).toString('ascii') === 'RIFF'
        && bytes.subarray(8, 12).toString('ascii') === 'WAVE';
}
// call before decodeAudioHex(..., 'wav'): if (!looksLikeWav(data.audio)) request mp3 or retry

Type guard

function isWavBuffer(bytes) {
    return Buffer.isBuffer(bytes) && bytes.length >= 12
        && bytes.subarray(0, 4).toString('ascii') === 'RIFF'
        && bytes.subarray(8, 12).toString('ascii') === 'WAVE';
}

Try / catch

try {
    const bytes = decodeAudioHex(data.audio, expectedBytes, 'wav');
} catch (e) {
    if (String(e.message).includes('not a WAV file')) {
        // fall back: detect actual format and save accordingly
        const mp3 = decodeAudioHex(data.audio, expectedBytes, 'mp3');
        fs.writeFileSync(out + '.mp3', mp3);
    } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/50b0f28303874bba. Report an issue: GitHub.