{"record":{"id":"a86fcdd05407fcf2","repo":"laurent22/joplin","slug":"could-not-download-from-modelurl-error-respo","errorCode":null,"errorMessage":"Could not download from ${modelUrl}: Error ${response.status}","messagePattern":"Could not download from (.+?): Error (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/app-mobile/services/voiceTyping/VoiceTyping.ts","lineNumber":107,"sourceCode":"\n\tpublic async clearDownloads() {\n\t\tawait this.provider.deleteCachedModels(this.locale);\n\t}\n\n\tpublic async download() {\n\t\tconst modelPath = this.getModelPath();\n\t\tconst modelUrl = this.provider.getDownloadUrl(this.locale);\n\n\t\tawait shim.fsDriver().remove(modelPath);\n\t\tlogger.info(`Downloading model from: ${modelUrl}`);\n\n\t\tconst isZipped = modelUrl.endsWith('.zip');\n\t\tconst downloadPath = isZipped ? `${modelPath}.zip` : modelPath;\n\t\tconst response = await shim.fetchBlob(modelUrl, {\n\t\t\tpath: downloadPath,\n\t\t});\n\n\t\tif (!response.ok || response.status >= 400) throw new Error(`Could not download from ${modelUrl}: Error ${response.status}`);\n\n\t\tif (isZipped) {\n\t\t\tconst modelName = this.provider.modelName;\n\t\t\tconst unzipDir = `${shim.fsDriver().getCacheDirectoryPath()}/voice-typing-extract/${modelName}/${this.locale}`;\n\t\t\ttry {\n\t\t\t\tlogger.info(`Unzipping ${downloadPath} => ${unzipDir}`);\n\n\t\t\t\tawait unzip(downloadPath, unzipDir);\n\n\t\t\t\tconst contents = await shim.fsDriver().readDirStats(unzipDir);\n\t\t\t\tif (contents.length !== 1) {\n\t\t\t\t\tlogger.error('Expected 1 file or directory but got', contents);\n\t\t\t\t\tthrow new Error(`Expected 1 file or directory, but got ${contents.length}`);\n\t\t\t\t}\n\n\t\t\t\tconst fullUnzipPath = `${unzipDir}/${contents[0].path}`;\n\n\t\t\t\tlogger.info(`Moving ${fullUnzipPath} => ${modelPath}`);","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/app-mobile/services/voiceTyping/VoiceTyping.ts#L89-L125","documentation":"Thrown by `VoiceTyping.download()` when `shim.fetchBlob` returns a non-OK or 4xx/5xx response for the provider's model download URL. Used to abort the model install before attempting unzip/move. The URL and HTTP status are interpolated so the failure source is identifiable.","triggerScenarios":"The provider's `getDownloadUrl(locale)` returns a URL that 404s, the host is unreachable (network/DNS), the CDN returns 5xx, or a redirect was not followed. Also when `response.ok` is false for any reason.","commonSituations":"Model URL changed upstream but the bundled provider still points at the old path; offline or metered connection drops mid-download; regional CDN block; the locale has no published model file.","solutions":["Check network connectivity and retry the download (transient 5xx/timeout).","Verify the provider's `getDownloadUrl(locale)` returns a live URL for the requested locale.","Update the app / provider config to point at the current model distribution URL.","Catch the error, surface it to the user, and offer a retry with exponential backoff."],"exampleFix":"// before\nconst response = await shim.fetchBlob(modelUrl, { path: downloadPath });\nif (!response.ok || response.status >= 400) throw new Error(`Could not download from ${modelUrl}: Error ${response.status}`);\n\n// after\nconst response = await shim.fetchBlob(modelUrl, { path: downloadPath });\nif (!response.ok || response.status >= 400) {\n  throw new Error(`Could not download from ${modelUrl}: Error ${response.status}`);\n}\n// wrap caller in retry-on-5xx","handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"const downloadWithRetry = async (url, downloadPath, attempts = 3) => {\n  for (let i = 0; i < attempts; i++) {\n    const response = await shim.fetchBlob(url, { path: downloadPath });\n    if (response.ok && response.status < 400) return response;\n    if (response.status < 500) break; // do not retry 4xx\n    await new Promise(r => setTimeout(r, 2 ** i * 500));\n  }\n  throw new Error(`Could not download from ${url}`);\n};","preventionTips":["Verify the provider download URL is reachable for the requested locale.","Retry on 5xx with exponential backoff; do not retry 4xx.","Keep the provider's URL config up to date with the current distribution host."],"tags":["network","voice-typing","download","mobile"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}