{"record":{"id":"427957c67953381c","repo":"remotion-dev/remotion","slug":"model-must-be-a-string","errorCode":null,"errorMessage":"model must be a string.","messagePattern":"model must be a string\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/studio/src/components/WebMcp.tsx","lineNumber":514,"sourceCode":"\t\t\t\t\t\t\treturn missingOptionalPackageResult(WHISPER_WEBGPU_PACKAGE);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst assetPath = resolveAssetPath({\n\t\t\t\t\t\t\tassetPath: input.assetPath,\n\t\t\t\t\t\t\tcurrentContent: currentContentRef.current,\n\t\t\t\t\t\t\tstaticFiles: staticFilesRef.current,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst fileType = getPreviewFileType(assetPath);\n\t\t\t\t\t\tif (fileType !== 'audio' && fileType !== 'video') {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t'The transcription asset must be audio or video.',\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst whisper = await import('@remotion/whisper-webgpu');\n\t\t\t\t\t\tconst modelName = input.model ?? 'small.en';\n\t\t\t\t\t\tif (typeof modelName !== 'string') {\n\t\t\t\t\t\t\tthrow new Error('model must be a string.');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst model = whisper\n\t\t\t\t\t\t\t.getAvailableModels()\n\t\t\t\t\t\t\t.find((candidate) => candidate.name === modelName);\n\t\t\t\t\t\tif (!model) {\n\t\t\t\t\t\t\tthrow new Error(`Unknown Whisper model: ${modelName}.`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst task = input.task ?? 'transcribe';\n\t\t\t\t\t\tif (task !== 'transcribe' && task !== 'translate') {\n\t\t\t\t\t\t\tthrow new Error('task must be transcribe or translate.');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (task === 'translate' && !model.supportsTranslation) {\n\t\t\t\t\t\t\tthrow new Error(`${model.name} does not support translation.`);\n\t\t\t\t\t\t}\n","sourceCodeStart":496,"sourceCodeEnd":532,"githubUrl":"https://github.com/remotion-dev/remotion/blob/b2f4e34732f3c6c222ea029413e22dc402218cd7/packages/studio/src/components/WebMcp.tsx#L496-L532","documentation":"The tool defaults model to 'small.en' and then asserts it is a string before matching it against available Whisper models. Because the default guarantees a string in normal flow, this throw mainly protects against corrupted input state where model resolves to a non-string value that bypasses the nullish coalescing (e.g. explicit null or object).","triggerScenarios":"Calling the WebMcp transcribe tool with input.model explicitly set to null, a number, an object, or another non-string non-undefined value.","commonSituations":"Agents sending JSON with null instead of omitting the field; schema-less tool callers passing wrong types; handcrafted requests with model: 2.","solutions":["Omit the model argument to use the 'small.en' default, or pass a valid model name string like 'small.en'.","Ensure model is a string, not null or a number.","Match the name against whisper.getAvailableModels() before calling.","Fix the calling client's schema so model is string | undefined."],"exampleFix":"// before\nawait tool.execute({assetPath: 'a.mp3', model: null});\n// after\nawait tool.execute({assetPath: 'a.mp3'}); // defaults to 'small.en'","handlingStrategy":"type-guard","validationCode":"if (model !== undefined && typeof model !== 'string') {\n  throw new Error('model must be a string or omitted.');\n}","typeGuard":"const isModelName = (v: unknown): v is string =>\n  typeof v === 'string' && v.length > 0;","tryCatchPattern":"try {\n  await transcribeTool({assetPath, model});\n} catch (e) {\n  if (e.message === 'model must be a string.') {\n    await transcribeTool({assetPath}); // use default\n  }\n}","preventionTips":["Omit model instead of sending null.","Type tool inputs as {model?: string}.","Validate against getAvailableModels() before calls."],"tags":["mcp","whisper","type-mismatch"],"backgroundTag":"type-mismatch","analyzedSha":"b2f4e34732f3c6c222ea029413e22dc402218cd7","analyzedAt":"2026-09-09T13:27:51.281Z","contentChangedAt":"2026-09-09T13:27:51.281Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}