windmill-labs/windmill · error

res.error ?? 'discard failed'

Error message

res.error ?? 'discard failed'

What it means

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.

Source

Thrown at frontend/src/lib/components/home/bulkActions.ts:124

	})
}

async function deleteItem(ctx: BulkContext, item: BulkItem): Promise<void> {
	if (item.kind === 'script') {
		await ScriptService.deleteScriptByPath({ workspace: ctx.workspace, path: item.path })
	} else if (item.kind === 'flow') {
		await FlowService.deleteFlowByPath({ workspace: ctx.workspace, path: item.path })
	} else {
		await AppService.deleteApp({ workspace: ctx.workspace, path: item.path })
	}
}

async function discardItemDraft(ctx: BulkContext, item: BulkItem): Promise<void> {
	// The draft overlay is the one place a raw app is its own kind.
	const kind = item.kind === 'app' && item.rawApp ? 'raw_app' : item.kind
	// invalidate=false: the caller refreshes the draft list once for the batch.
	const res = await discardDraft(kind, item.path, ctx.workspace, item.draftOnly, false, false)
	if (!res.success) throw new Error(res.error ?? 'discard failed')
}

export type BulkOutcome = { item: BulkItem; error?: string }

/**
 * Apply `action` to each item in turn, never aborting the batch on a failure —
 * every item gets its own outcome so partial success is reported rather than
 * hidden. `onProgress` is called after each item with the number completed.
 */
export async function runBulk(
	action: BulkAction,
	items: BulkItem[],
	ctx: BulkContext,
	opts: { target?: string; onProgress?: (done: number) => void } = {}
): Promise<BulkOutcome[]> {
	const outcomes: BulkOutcome[] = []
	for (const item of items) {
		try {

View on GitHub (pinned to e474e8803c)

Solutions

  1. Refresh the drafts list and retry the bulk discard on the remaining items.
  2. Check res.error in the bulk outcome for the specific backend message (404 vs 403) and act on it.
  3. Verify workspace and permissions for the failing item's path.
  4. Re-run the bulk action — runBulk continues per item, so only failed items remain.

Example fix

// before
await runBulk('discard', items)
// after
const outcomes = await runBulk('discard', items)
const stale = outcomes.filter(o => o.error).map(o => o.item)
await refreshDrafts(); await runBulk('discard', stale)
Defensive patterns

Strategy: try-catch

Validate before calling

const fresh = await listDrafts(workspace)
const discardable = items.filter(i => fresh.some(d => d.path === i.path))

Try / catch

const outcomes = await runBulk(ctx, 'discard', items, opts)
for (const o of outcomes.filter(o => o.error)) console.warn('discard failed', o.item.path, o.error)

Prevention

When it happens

Trigger: 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.

Common situations: Two people discarding the same drafts concurrently; stale draft list opened while drafts were deleted elsewhere; permission loss after the list was loaded.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/b7c26e84e109bd02. Report an issue: GitHub.