jamiepine/voicebox · warning · HTTPException

Invalid model size '{model_size}'. Must be one of: {', '.joi

Error message

Invalid model size '{model_size}'. Must be one of: {', '.join(valid_sizes)}

What it means

Raised (HTTP 400) by POST /transcribe when the effective model_size is not a key in WHISPER_HF_REPOS. model_size resolves to the `model` form field if provided, otherwise to the currently loaded whisper model's model_size. So the error can fire even when the client omits `model`, if the server's configured default is invalid. The valid set is the WHISPER_HF_REPOS keys (e.g. tiny/base/small/medium/large variants).

Source

Thrown at backend/routes/transcription.py:62

        duration = len(audio) / sr

        # The STT backend (mlx_audio.stt -> miniaudio) only decodes
        # WAV/FLAC/MP3/Vorbis, so browser recordings uploaded as WebM/Opus
        # fail with "unsupported file format" (issue: web-mode dictation).
        # librosa already decoded the file above (it falls back to
        # audioread/ffmpeg for exotic containers), so re-encode that PCM to a
        # temp WAV and hand *that* to Whisper. WAV inputs pass through
        # unchanged.
        if file_suffix != ".wav":
            stt_path = f"{tmp_path}.stt.wav"
            await asyncio.to_thread(save_audio, audio, stt_path, sr)

        whisper_model = transcribe.get_whisper_model()
        model_size = model if model else whisper_model.model_size

        valid_sizes = list(WHISPER_HF_REPOS.keys())
        if model_size not in valid_sizes:
            raise HTTPException(
                status_code=400,
                detail=f"Invalid model size '{model_size}'. Must be one of: {', '.join(valid_sizes)}",
            )

        already_loaded = whisper_model.is_loaded() and whisper_model.model_size == model_size
        if not already_loaded and not whisper_model._is_model_cached(model_size):
            progress_model_name = f"whisper-{model_size}"
            task_manager = get_task_manager()

            async def download_whisper_background():
                try:
                    await whisper_model.load_model_async(model_size)
                    task_manager.complete_download(progress_model_name)
                except Exception as e:
                    task_manager.error_download(progress_model_name, str(e))

            task_manager.start_download(progress_model_name)
            create_background_task(download_whisper_background())

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Fetch the accepted model list from the backend (STT model configs endpoint) and present only those to the user.
  2. If hit when omitting model, fix the server's default whisper model_size to a value present in WHISPER_HF_REPOS.
  3. After upgrading the backend, re-check WHISPER_HF_REPOS keys and align any saved settings.
  4. Strip version suffixes the backend doesn't register (e.g. send 'large' not 'large-v3' if only 'large' is a key).

Example fix

# before
whisper_model = transcribe.get_whisper_model()
model_size = model if model else whisper_model.model_size
if model_size not in valid_sizes:
    raise HTTPException(400, f"Invalid model size '{model_size}'...")

# after (validate explicitly + fail fast on a bad default)
if model is not None and model not in valid_sizes:
    raise HTTPException(400, f"Invalid model '{model}'. Must be one of: {', '.join(valid_sizes)}")
model_size = model or whisper_model.model_size
assert model_size in valid_sizes, f"server default model '{model_size}' not in WHISPER_HF_REPOS"
Defensive patterns

Strategy: validation

Validate before calling

async function transcribeSafe(api, file, language, model) {
  const allowed = await api.getSttModelConfigs().then(r => r.map(c => c.id));
  const chosen = model ?? defaultSttModel;
  if (!allowed.includes(chosen)) {
    throw new Error(`model must be one of ${allowed.join(', ')}`);
  }
  return api.transcribe(file, language, model);
}

Type guard

function isValidModelSize(allowed, m) {
  return Array.isArray(allowed) && typeof m === 'string' && allowed.includes(m);
}

Try / catch

try { await api.transcribe(file, language, model); }
catch (e) {
  if (e.status === 400 && /Invalid model size/.test(e.detail)) {
    refreshModelList(); // pick from current set
  } else if (e.status === 202) {
    notify('Model downloading; retry shortly');
  } else throw e;
}

Prevention

When it happens

Trigger: POST /transcribe with model="large-v3" when the build only ships keys like tiny/base/small; or omitting model while the server's default whisper model_size is set to a value not present in WHISPER_HF_REPOS (mismatched config after an upgrade).

Common situations: Version skew: WHISPER_HF_REPOS was narrowed (a model dropped) but a persisted default still references it; client hardcodes a model id that the backend doesn't register; typo in the model form field.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/64ae72bef9a23947. Report an issue: GitHub.