{"record":{"id":"850f88da4f9b7842","repo":"laurent22/joplin","slug":"batch-delete-failed-json-stringify-check","errorCode":null,"errorMessage":"Batch delete failed? ${JSON.stringify(check)}","messagePattern":"Batch delete failed\\? (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/lib/file-api-driver-dropbox.js","lineNumber":248,"sourceCode":"\t}\n\n\tasync clearRoot() {\n\t\tconst entries = await this.list('');\n\t\tconst batchDelete = [];\n\t\tfor (let i = 0; i < entries.items.length; i++) {\n\t\t\tbatchDelete.push({ path: this.makePath_(entries.items[i].path) });\n\t\t}\n\n\t\tconst response = await this.api().exec('POST', 'files/delete_batch', { entries: batchDelete });\n\t\tconst jobId = response.async_job_id;\n\n\t\twhile (true) {\n\t\t\tconst check = await this.api().exec('POST', 'files/delete_batch/check', { async_job_id: jobId });\n\t\t\tif (check['.tag'] === 'complete') break;\n\n\t\t\t// It returns \"failed\" if it didn't work but anyway throw an error if it's anything other than complete or in_progress\n\t\t\tif (check['.tag'] !== 'in_progress') {\n\t\t\t\tthrow new Error(`Batch delete failed? ${JSON.stringify(check)}`);\n\t\t\t}\n\t\t\tawait time.sleep(2);\n\t\t}\n\t}\n}\n\nmodule.exports = { FileApiDriverDropbox };\n","sourceCodeStart":230,"sourceCodeEnd":256,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/lib/file-api-driver-dropbox.js#L230-L256","documentation":"Thrown by the Dropbox driver while polling files/delete_batch/check. The loop accepts only 'complete' (success) and 'in_progress' (keep waiting); any other '.tag' (notably 'failed') aborts with the raw check response JSON. It surfaces Dropbox-side failures of a bulk delete job that the API reports asynchronously.","triggerScenarios":"Calling this.delete() or clearRoot() with many paths so the driver dispatches files/delete_batch, then polling the job. Dropbox returns check['.tag'] === 'failed' (or any tag other than complete/in_progress) — e.g. a path inside the batch is missing, permission-denied, rate-limited, or the job hit an internal error.","commonSituations":"One entry in the batch references a non-existent path; Dropbox API quota/rate limiting; app permissions revoked mid-sync; transient Dropbox backend error; very large batch where a subset of entries fails.","solutions":["Inspect the JSON in the message — the check response contains a 'failures' array naming which entries errored and why; fix or remove those paths and retry.","Verify the Dropbox app has files.content.delete scope and the account still authorizes the app (re-link the sync target).","Reduce batch size or pre-filter paths that no longer exist before calling delete.","Retry the sync — transient 'failed' tags from rate limiting often clear on the next run."],"exampleFix":"// before\nawait driver.deleteMany(paths);\n// after - pre-filter existing paths and surface partial failures\nconst existing = [];\nfor (const p of paths) {\n  if (await driver.stat(p)) existing.push(p);\n}\n// wrap call and, on failure, log check.failures for granular handling\ntry { await driver.deleteMany(existing); }\ncatch (e) { if (/Batch delete failed/.test(e.message)) logger.error('Dropbox partial:', e.message); throw e; }","handlingStrategy":"retry","validationCode":"// Verify paths exist and the job hasn't already failed before relying on the batch.\nconst valid = [];\nfor (const p of paths) { if (await driver.stat(p)) valid.push(p); }\nif (!valid.length) return; // nothing to delete","typeGuard":"function isDropboxBatchFailed(check: any): check is { '.tag': 'failed'; failures?: any[] } {\n  return check && check['.tag'] === 'failed';\n}","tryCatchPattern":"try {\n  await driver.deleteMany(paths);\n} catch (error) {\n  const m = error.message.match(/Batch delete failed\\? (.*)/s);\n  if (m) {\n    const check = JSON.parse(m[1]);\n    if (isDropboxBatchFailed(check) && check.failures?.length) {\n      // retry only the non-failed entries, or report partial\n    }\n  }\n  throw error;\n}","preventionTips":["Pre-filter paths via stat() to avoid batching non-existent entries.","Keep batch sizes moderate to reduce partial-failure blast radius.","Log the full check response so failures are diagnosable.","Confirm the Dropbox app retains files.content.delete scope before large syncs."],"tags":["dropbox","sync","batch-delete","network"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}