{"record":{"id":"b7c26e84e109bd02","repo":"windmill-labs/windmill","slug":"res-error-discard-failed","errorCode":null,"errorMessage":"res.error ?? 'discard failed'","messagePattern":"res\\.error \\?\\? 'discard failed'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/home/bulkActions.ts","lineNumber":124,"sourceCode":"\t})\n}\n\nasync function deleteItem(ctx: BulkContext, item: BulkItem): Promise<void> {\n\tif (item.kind === 'script') {\n\t\tawait ScriptService.deleteScriptByPath({ workspace: ctx.workspace, path: item.path })\n\t} else if (item.kind === 'flow') {\n\t\tawait FlowService.deleteFlowByPath({ workspace: ctx.workspace, path: item.path })\n\t} else {\n\t\tawait AppService.deleteApp({ workspace: ctx.workspace, path: item.path })\n\t}\n}\n\nasync function discardItemDraft(ctx: BulkContext, item: BulkItem): Promise<void> {\n\t// The draft overlay is the one place a raw app is its own kind.\n\tconst kind = item.kind === 'app' && item.rawApp ? 'raw_app' : item.kind\n\t// invalidate=false: the caller refreshes the draft list once for the batch.\n\tconst res = await discardDraft(kind, item.path, ctx.workspace, item.draftOnly, false, false)\n\tif (!res.success) throw new Error(res.error ?? 'discard failed')\n}\n\nexport type BulkOutcome = { item: BulkItem; error?: string }\n\n/**\n * Apply `action` to each item in turn, never aborting the batch on a failure —\n * every item gets its own outcome so partial success is reported rather than\n * hidden. `onProgress` is called after each item with the number completed.\n */\nexport async function runBulk(\n\taction: BulkAction,\n\titems: BulkItem[],\n\tctx: BulkContext,\n\topts: { target?: string; onProgress?: (done: number) => void } = {}\n): Promise<BulkOutcome[]> {\n\tconst outcomes: BulkOutcome[] = []\n\tfor (const item of items) {\n\t\ttry {","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/home/bulkActions.ts#L106-L142","documentation":"discardItemDraft calls discardDraft and throws when the API reports success=false, surfacing the backend error message (or a generic 'discard failed'). It is the per-item worker for the bulk-discard action.","triggerScenarios":"Bulk-discard run against an item whose draft no longer exists (already discarded), a path the user no longer has permission for, or a workspace/API error returned in res.error.","commonSituations":"Two people discarding the same drafts concurrently; stale draft list opened while drafts were deleted elsewhere; permission loss after the list was loaded.","solutions":["Refresh the drafts list and retry the bulk discard on the remaining items.","Check res.error in the bulk outcome for the specific backend message (404 vs 403) and act on it.","Verify workspace and permissions for the failing item's path.","Re-run the bulk action — runBulk continues per item, so only failed items remain."],"exampleFix":"// before\nawait runBulk('discard', items)\n// after\nconst outcomes = await runBulk('discard', items)\nconst stale = outcomes.filter(o => o.error).map(o => o.item)\nawait refreshDrafts(); await runBulk('discard', stale)","handlingStrategy":"try-catch","validationCode":"const fresh = await listDrafts(workspace)\nconst discardable = items.filter(i => fresh.some(d => d.path === i.path))","typeGuard":null,"tryCatchPattern":"const outcomes = await runBulk(ctx, 'discard', items, opts)\nfor (const o of outcomes.filter(o => o.error)) console.warn('discard failed', o.item.path, o.error)","preventionTips":["Refresh the draft list before bulk discards in shared workspaces","Check per-item BulkOutcome errors instead of assuming all succeeded","Verify permissions on the target workspace before the batch"],"tags":["drafts","bulk-actions","api-error"],"backgroundTag":"draft-discard-failed","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}