{"record":{"id":"c011f277a6cf74ce","repo":"laurent22/joplin","slug":"fetch-failed-result-statustext","errorCode":null,"errorMessage":"fetch failed: ${result.statusText}","messagePattern":"fetch failed: (.+?)","errorType":"exception","errorClass":"JoplinError","httpStatus":null,"severity":"error","filePath":"packages/app-mobile/utils/shim-init-react/index.web.ts","lineNumber":60,"sourceCode":"\t\treturn fsDriver_;\n\t};\n\tshim.fsDriver = fsDriver;\n\tshim.crypto = joplinCrypto;\n\n\tshim.randomBytes = async (count: number) => {\n\t\tconst buffer = new Uint8Array(count);\n\t\tcrypto.getRandomValues(buffer);\n\n\t\treturn [...buffer];\n\t};\n\n\tshim.fetchBlob = async function(url, options: FetchBlobOptions) {\n\t\tconst outputPath = options.path;\n\t\tif (!outputPath) throw new Error('fetchBlob: Missing outputPath');\n\n\t\tconst result = await fetch(url, { method: options.method, headers: options.headers });\n\t\tif (!result.ok) {\n\t\t\tthrow new JoplinError(`fetch failed: ${result.statusText}`, result.status);\n\t\t}\n\n\t\tconst blob = await result.blob();\n\t\tawait fsDriver().writeFile(outputPath, await blob.arrayBuffer(), 'Buffer');\n\n\t\treturn {\n\t\t\tok: result.ok,\n\t\t\tpath: outputPath,\n\t\t\ttext: () => {\n\t\t\t\treturn result.statusText;\n\t\t\t},\n\t\t\tjson: () => {\n\t\t\t\treturn { message: `${result.status}: ${result.statusText}` };\n\t\t\t},\n\t\t\tstatus: result.status,\n\t\t\theaders: result.headers,\n\t\t};\n\t};","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/app-mobile/utils/shim-init-react/index.web.ts#L42-L78","documentation":"Thrown by the web-platform shim.fetchBlob when the browser fetch() resolves with result.ok === false (HTTP status >= 400). Unlike the mobile variant, this constructs a JoplinError carrying result.status as the error code, with the message 'fetch failed: <statusText>'. Used when running Joplin's web build (e.g. the browser/Electron-web target).","triggerScenarios":"Server returns 4xx/5xx (404 for missing resource, 401/403 auth, 500 server error, 502/503 gateway); CORS preflight rejection surfaces as a non-ok response; the sync endpoint moved and returns 404; rate limiting (429).","commonSituations":"Web build downloading an attachment the server cannot find; auth cookie/token expired; CORS misconfiguration on the sync target; transient 5xx during server maintenance; proxy returning 502.","solutions":["Inspect the JoplinError.code (it is result.status) to branch on 401 (re-auth), 404 (missing), 429 (back off), 5xx (retry).","Verify the URL is correct and the resource exists server-side.","Check CORS headers on the response if running in a browser — a CORS failure may manifest as a network error or non-ok response.","Retry with backoff for 5xx and 429; do not retry 4xx (except 408/429)."],"exampleFix":"// before\nawait shim.fetchBlob(url, { path, method: 'GET' });\n// after — branch on the carried status code\ntry {\n\tawait shim.fetchBlob(url, { path, method: 'GET' });\n} catch (e) {\n\tif (e instanceof JoplinError && e.code === 401) await refreshToken();\n\telse if (e instanceof JoplinError && e.code >= 500) await retryWithBackoff();\n\telse throw e;\n}","handlingStrategy":"try-catch","validationCode":"try { new URL(url); } catch { throw new Error(`Invalid URL: ${url}`); }\nawait shim.fetchBlob(url, { path, method: 'GET' });","typeGuard":"function isJoplinError(e) { return e && typeof e.code === 'number'; }","tryCatchPattern":"try {\n  await shim.fetchBlob(url, { path, method: 'GET' });\n} catch (e) {\n  if (e instanceof JoplinError) {\n    if (e.code === 401) await refreshToken();\n    else if (e.code === 429 || e.code >= 500) await retryWithBackoff();\n    else throw e;\n  } else throw e;\n}","preventionTips":["Branch on JoplinError.code for status-specific handling.","Verify CORS configuration on the sync target for browser builds.","Do not retry deterministic 4xx errors (except 408/429)."],"tags":["web","network","fetch","http-status","cors"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}