jackwener/OpenCLI · error · CommandExecutionError

MiniMax music returned bytes that are not an MP3 file

Error message

MiniMax music returned bytes that are not an MP3 file

What it means

decodeAudioHex validates that hex-decoded audio matches the requested MP3 container: isMp3() accepts bytes starting with an 'ID3' tag or a valid MPEG frame sync (0xFF followed by 0xE0-masked byte). If neither signature is present, the library throws this CommandExecutionError because MiniMax returned audio that is not decodable MP3.

Source

Thrown at clis/minimax/utils.js:160

    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}`);
    return path.resolve(raw);
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Check the request sets audio_setting.format='mp3' and regenerate
  2. Inspect the first bytes of the decoded buffer — if they read 'RIFF'/'WAVE', switch to format='wav' and save with a .wav extension
  3. Retry the API call; capture base_resp/trace_id if it recurs and contact MiniMax support
  4. Validate data.audio hex integrity upstream (no truncation or whitespace) before decode

Example fix

// before
const bytes = decodeAudioHex(data.audio, expectedBytes, 'mp3'); // got RIFF bytes -> throws
// after: align with the actual payload format
if (bytes.subarray(0, 4).toString('ascii') === 'RIFF') {
    fs.writeFileSync('out.wav', decodeAudioHex(data.audio, expectedBytes, 'wav'));
}
Defensive patterns

Strategy: validation

Validate before calling

function looksLikeMp3(hex) {
    if (!/^[0-9a-f]+$/i.test(hex) || hex.length % 2 !== 0) return false;
    const bytes = Buffer.from(hex, 'hex');
    return bytes.length >= 2
        && (bytes.subarray(0, 3).toString('ascii') === 'ID3'
            || (bytes[0] === 0xff && (bytes[1] & 0xe0) === 0xe0));
}
// before decodeAudioHex(..., 'mp3'): if (!looksLikeMp3(data.audio)) switch format or retry

Type guard

function isMp3Buffer(bytes) {
    return Buffer.isBuffer(bytes) && bytes.length >= 2
        && (bytes.subarray(0, 3).toString('ascii') === 'ID3'
            || (bytes[0] === 0xff && (bytes[1] & 0xe0) === 0xe0));
}

Try / catch

try {
    const bytes = decodeAudioHex(data.audio, expectedBytes, 'mp3');
} catch (e) {
    if (String(e.message).includes('not an MP3 file')) {
        const wav = decodeAudioHex(data.audio, expectedBytes, 'wav');
        fs.writeFileSync(out + '.wav', wav);
    } else throw e;
}

Prevention

When it happens

Trigger: Calling decodeAudioHex with format='mp3' while the decoded bytes lack an ID3 header and MPEG sync word — typically WAV (RIFF) bytes returned despite audio_setting.format='mp3', or corrupt/truncated data.

Common situations: Requesting mp3 but the MiniMax endpoint emits WAV; the hex payload is truncated so the first frame bytes are missing; intermediate proxies altering the payload.

Related errors


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