{"record":{"id":"64ae72bef9a23947","repo":"jamiepine/voicebox","slug":"invalid-model-size-model-size-must-be-one-of","errorCode":null,"errorMessage":"Invalid model size '{model_size}'. Must be one of: {', '.join(valid_sizes)}","messagePattern":"Invalid model size '(.+?)'\\. Must be one of: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"backend/routes/transcription.py","lineNumber":62,"sourceCode":"        duration = len(audio) / sr\n\n        # The STT backend (mlx_audio.stt -> miniaudio) only decodes\n        # WAV/FLAC/MP3/Vorbis, so browser recordings uploaded as WebM/Opus\n        # fail with \"unsupported file format\" (issue: web-mode dictation).\n        # librosa already decoded the file above (it falls back to\n        # audioread/ffmpeg for exotic containers), so re-encode that PCM to a\n        # temp WAV and hand *that* to Whisper. WAV inputs pass through\n        # unchanged.\n        if file_suffix != \".wav\":\n            stt_path = f\"{tmp_path}.stt.wav\"\n            await asyncio.to_thread(save_audio, audio, stt_path, sr)\n\n        whisper_model = transcribe.get_whisper_model()\n        model_size = model if model else whisper_model.model_size\n\n        valid_sizes = list(WHISPER_HF_REPOS.keys())\n        if model_size not in valid_sizes:\n            raise HTTPException(\n                status_code=400,\n                detail=f\"Invalid model size '{model_size}'. Must be one of: {', '.join(valid_sizes)}\",\n            )\n\n        already_loaded = whisper_model.is_loaded() and whisper_model.model_size == model_size\n        if not already_loaded and not whisper_model._is_model_cached(model_size):\n            progress_model_name = f\"whisper-{model_size}\"\n            task_manager = get_task_manager()\n\n            async def download_whisper_background():\n                try:\n                    await whisper_model.load_model_async(model_size)\n                    task_manager.complete_download(progress_model_name)\n                except Exception as e:\n                    task_manager.error_download(progress_model_name, str(e))\n\n            task_manager.start_download(progress_model_name)\n            create_background_task(download_whisper_background())","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/transcription.py#L44-L80","documentation":"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).","triggerScenarios":"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).","commonSituations":"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.","solutions":["Fetch the accepted model list from the backend (STT model configs endpoint) and present only those to the user.","If hit when omitting model, fix the server's default whisper model_size to a value present in WHISPER_HF_REPOS.","After upgrading the backend, re-check WHISPER_HF_REPOS keys and align any saved settings.","Strip version suffixes the backend doesn't register (e.g. send 'large' not 'large-v3' if only 'large' is a key)."],"exampleFix":"# before\nwhisper_model = transcribe.get_whisper_model()\nmodel_size = model if model else whisper_model.model_size\nif model_size not in valid_sizes:\n    raise HTTPException(400, f\"Invalid model size '{model_size}'...\")\n\n# after (validate explicitly + fail fast on a bad default)\nif model is not None and model not in valid_sizes:\n    raise HTTPException(400, f\"Invalid model '{model}'. Must be one of: {', '.join(valid_sizes)}\")\nmodel_size = model or whisper_model.model_size\nassert model_size in valid_sizes, f\"server default model '{model_size}' not in WHISPER_HF_REPOS\"","handlingStrategy":"validation","validationCode":"async function transcribeSafe(api, file, language, model) {\n  const allowed = await api.getSttModelConfigs().then(r => r.map(c => c.id));\n  const chosen = model ?? defaultSttModel;\n  if (!allowed.includes(chosen)) {\n    throw new Error(`model must be one of ${allowed.join(', ')}`);\n  }\n  return api.transcribe(file, language, model);\n}","typeGuard":"function isValidModelSize(allowed, m) {\n  return Array.isArray(allowed) && typeof m === 'string' && allowed.includes(m);\n}","tryCatchPattern":"try { await api.transcribe(file, language, model); }\ncatch (e) {\n  if (e.status === 400 && /Invalid model size/.test(e.detail)) {\n    refreshModelList(); // pick from current set\n  } else if (e.status === 202) {\n    notify('Model downloading; retry shortly');\n  } else throw e;\n}","preventionTips":["Source the accepted model sizes from the backend, not a hardcoded client list.","After an upgrade, re-align any persisted default STT model with WHISPER_HF_REPOS keys.","Omit `model` to use the server default only after confirming that default is valid."],"tags":["api","transcription","whisper","validation","configuration"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}