{"record":{"id":"2c0338a6da6cbac3","repo":"tldraw/tldraw","slug":"async-export-finished-but-no-bundle-url-was-return","errorCode":null,"errorMessage":"Async export finished but no bundle URL was returned: ${JSON.stringify(processResult, null, 2)}","messagePattern":"Async export finished but no bundle URL was returned: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"internal/scripts/i18n-download-strings.ts","lineNumber":84,"sourceCode":"\t\tthrow new Error(`Async export did not return process_id: ${JSON.stringify(start, null, 2)}`)\n\t}\n\n\tfor (let i = 0; i < ASYNC_EXPORT_MAX_POLLS; i++) {\n\t\tconst processResult = await lokaliseRequest<{\n\t\t\tprocess?: {\n\t\t\t\tstatus?: string\n\t\t\t\tmessage?: string\n\t\t\t\tdetails?: { download_url?: string; bundle_url?: string }\n\t\t\t}\n\t\t}>(`/projects/${projectId}/processes/${start.process_id}`, { method: 'GET' }, apiKey)\n\n\t\tconst status = processResult.process?.status\n\t\tconst details = processResult.process?.details\n\t\tconst bundleUrl = details?.download_url ?? details?.bundle_url\n\n\t\tif (status === 'finished') {\n\t\t\tif (!bundleUrl) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Async export finished but no bundle URL was returned: ${JSON.stringify(processResult, null, 2)}`\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn bundleUrl\n\t\t}\n\n\t\tif (status === 'failed' || status === 'cancelled') {\n\t\t\tthrow new Error(\n\t\t\t\t`Async export ${status}: ${processResult.process?.message ?? 'No message'}\\n${JSON.stringify(processResult, null, 2)}`\n\t\t\t)\n\t\t}\n\n\t\tawait sleep(ASYNC_EXPORT_POLL_INTERVAL_MS)\n\t}\n\n\tthrow new Error(\n\t\t`Async export timed out after ${ASYNC_EXPORT_MAX_POLLS} polls for project ${projectId}`\n\t)","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/tldraw/tldraw/blob/b31086b44731a7d1d9d46be4163ac8ef7417321d/internal/scripts/i18n-download-strings.ts#L66-L102","documentation":"When the async export process reaches status 'finished', the code expects details.download_url or details.bundle_url. Throws if both are absent on a finished process — Lokalise marked it done but returned no downloadable bundle.","triggerScenarios":"Lokalise bug returning finished status with empty details; the export produced an empty file set and Lokalise omitted the URL; response schema change relocating the URL field.","commonSituations":"Transient Lokalise-side issue; project with no files matching the filter; API version returning the URL under a different key.","solutions":["Re-run the export — transient Lokalise issues often clear on retry.","Inspect the full processResult JSON in the error to find the URL under a different field, and update the bundleUrl extraction.","Confirm the project actually has files to export (empty project → no bundle)."],"exampleFix":"// before\nconst bundleUrl = details?.download_url ?? details?.bundle_url\n// after (also accept a top-level url)\nconst bundleUrl = details?.download_url ?? details?.bundle_url ?? processResult.process?.url","handlingStrategy":"type-guard","validationCode":"const bundleUrl = details?.download_url ?? details?.bundle_url ?? (processResult as any).process?.url\nif (!bundleUrl) throw new Error('No bundle URL field on finished process')","typeGuard":"function hasBundleUrl(details: unknown): details is { download_url?: string; bundle_url?: string } {\n  return Boolean((details as any)?.download_url || (details as any)?.bundle_url)\n}","tryCatchPattern":null,"preventionTips":["Inspect full processResult JSON on failure to discover renamed fields.","Retry once on missing bundle URL — transient Lokalise issue.","Confirm the project has files before exporting."],"tags":["i18n","lokalise","api-contract","network"],"backgroundTag":null,"analyzedSha":"b31086b44731a7d1d9d46be4163ac8ef7417321d","analyzedAt":"2026-08-12T17:05:39.947Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}