laurent22/joplin · error · Error
Could not download from ${modelUrl}: Error ${response.status
Error message
Could not download from ${modelUrl}: Error ${response.status} What it means
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.
Source
Thrown at packages/app-mobile/services/voiceTyping/VoiceTyping.ts:107
public async clearDownloads() {
await this.provider.deleteCachedModels(this.locale);
}
public async download() {
const modelPath = this.getModelPath();
const modelUrl = this.provider.getDownloadUrl(this.locale);
await shim.fsDriver().remove(modelPath);
logger.info(`Downloading model from: ${modelUrl}`);
const isZipped = modelUrl.endsWith('.zip');
const downloadPath = isZipped ? `${modelPath}.zip` : modelPath;
const response = await shim.fetchBlob(modelUrl, {
path: downloadPath,
});
if (!response.ok || response.status >= 400) throw new Error(`Could not download from ${modelUrl}: Error ${response.status}`);
if (isZipped) {
const modelName = this.provider.modelName;
const unzipDir = `${shim.fsDriver().getCacheDirectoryPath()}/voice-typing-extract/${modelName}/${this.locale}`;
try {
logger.info(`Unzipping ${downloadPath} => ${unzipDir}`);
await unzip(downloadPath, unzipDir);
const contents = await shim.fsDriver().readDirStats(unzipDir);
if (contents.length !== 1) {
logger.error('Expected 1 file or directory but got', contents);
throw new Error(`Expected 1 file or directory, but got ${contents.length}`);
}
const fullUnzipPath = `${unzipDir}/${contents[0].path}`;
logger.info(`Moving ${fullUnzipPath} => ${modelPath}`);View on GitHub (pinned to 2654b33620)
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.
Example fix
// before
const response = await shim.fetchBlob(modelUrl, { path: downloadPath });
if (!response.ok || response.status >= 400) throw new Error(`Could not download from ${modelUrl}: Error ${response.status}`);
// after
const response = await shim.fetchBlob(modelUrl, { path: downloadPath });
if (!response.ok || response.status >= 400) {
throw new Error(`Could not download from ${modelUrl}: Error ${response.status}`);
}
// wrap caller in retry-on-5xx Defensive patterns
Strategy: retry
Validate before calling
null
Type guard
null
Try / catch
const downloadWithRetry = async (url, downloadPath, attempts = 3) => {
for (let i = 0; i < attempts; i++) {
const response = await shim.fetchBlob(url, { path: downloadPath });
if (response.ok && response.status < 400) return response;
if (response.status < 500) break; // do not retry 4xx
await new Promise(r => setTimeout(r, 2 ** i * 500));
}
throw new Error(`Could not download from ${url}`);
}; Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- fetchBlob: ${method} ${url}: ${error.toString()}
- No supported provider found!
- No model found at path: ${JSON.stringify(modelFolderPath)}
- Model not found at path ${modelPath}
- uploadBlob: ${method} ${url}: ${error.toString()}
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/a86fcdd05407fcf2.
Report an issue: GitHub.