{"record":{"id":"6c774066b8ce9ffa","repo":"windmill-labs/windmill","slug":"no-target-folder","errorCode":null,"errorMessage":"no target folder","messagePattern":"no target folder","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/home/bulkActions.ts","lineNumber":145,"sourceCode":"export 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 {\n\t\t\tswitch (action) {\n\t\t\t\tcase 'move':\n\t\t\t\t\tif (!opts.target) throw new Error('no target folder')\n\t\t\t\t\tawait moveItem(ctx, item, opts.target)\n\t\t\t\t\tbreak\n\t\t\t\tcase 'archive':\n\t\t\t\t\tawait setArchived(ctx, item, true)\n\t\t\t\t\tbreak\n\t\t\t\tcase 'unarchive':\n\t\t\t\t\tawait setArchived(ctx, item, false)\n\t\t\t\t\tbreak\n\t\t\t\tcase 'delete':\n\t\t\t\t\tawait deleteItem(ctx, item)\n\t\t\t\t\tbreak\n\t\t\t\tcase 'discard':\n\t\t\t\t\tawait discardItemDraft(ctx, item)\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t\toutcomes.push({ item })\n\t\t} catch (e: any) {\n\t\t\toutcomes.push({ item, error: e?.body ?? e?.message ?? String(e) })","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/home/bulkActions.ts#L127-L163","documentation":"runBulk's 'move' action requires opts.target (the destination folder path). It throws synchronously per item when target is missing, since moving without a destination is a caller bug, not an item failure.","triggerScenarios":"Calling runBulk('move', items, opts) with opts.target undefined/null — e.g. the UI move dialog was dismissed or the folder picker returned nothing.","commonSituations":"Button wired to 'move' before the user selected a destination folder; programmatic use passing only { workspace }; target cleared by form reset.","solutions":["Pass opts.target, e.g. runBulk(ctx, 'move', items, { workspace, target: 'folder/sub' }).","Disable the Move button in the UI until a folder is selected.","Guard in the caller: if (!target) return before invoking runBulk.","Use a folder picker component that cannot resolve to an empty path."],"exampleFix":"// before\nawait runBulk(ctx, 'move', items, { workspace })\n// after\nawait runBulk(ctx, 'move', items, { workspace, target: selectedFolder })","handlingStrategy":"validation","validationCode":"if (action === 'move' && !opts?.target) throw new Error('Select a destination folder first')","typeGuard":"function hasTarget(o) { return typeof o?.target === 'string' && o.target.length > 0 }","tryCatchPattern":"try { await runBulk(ctx, 'move', items, opts) } catch (e) { if (e.message === 'no target folder') openFolderPicker(); else throw e }","preventionTips":["Disable the Move action until a folder is selected in the picker","Always pass an explicit { target } when calling runBulk programmatically","Validate opts before entering the bulk loop"],"tags":["bulk-actions","missing-argument","validation"],"backgroundTag":"missing-required-option","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"}