{"record":{"id":"534f841aec54418f","repo":"windmill-labs/windmill","slug":"state-failuremessage-failed-to-delete-draft","errorCode":null,"errorMessage":"state.failureMessage ?? `Failed to delete draft \"${path}\".`","messagePattern":"state\\.failureMessage \\?\\? `Failed to delete draft \"(.+?)\"\\.`","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/global/userDraftAdapter.ts","lineNumber":609,"sourceCode":"\t\tUserDraft.remove(itemKind, storagePath, { workspace })\n\t} else {\n\t\tUserDraft.clear(itemKind, storagePath, { workspace })\n\t}\n\t// `remove`/`clear` only debounce the delete; persist it now so a deploy/discard\n\t// that the caller awaits has actually cleared the server draft on return.\n\tawait UserDraftDbSyncer.save({\n\t\tworkspace,\n\t\titemKind,\n\t\tpath: storagePath,\n\t\tvalue: null,\n\t\timmediate: true\n\t})\n\t// A failed (network/5xx) or conflicted delete is recorded in the syncer state,\n\t// not thrown — surface it so callers don't report the draft as removed while\n\t// the DB-backed source of truth still has it (same guard as the write path).\n\tconst state = UserDraftDbSyncer.getState({ workspace, itemKind, path: storagePath })\n\tif (state.state === 'failed') {\n\t\tthrow new Error(state.failureMessage ?? `Failed to delete draft \"${path}\".`)\n\t}\n\tif (UserDraftDbSyncer.getConflict({ workspace, itemKind, path: storagePath }).conflict) {\n\t\tthrow new Error(\n\t\t\t`Draft \"${path}\" changed externally since you last read it; it was not removed. Re-read and retry.`\n\t\t)\n\t}\n\tinvalidateWorkspaceDrafts(workspace)\n}\n\n/** Kind-addressed live-editor storage resolution (friendly → storage path),\n * for callers that must probe several draft kinds per chat type. */\nexport function resolveGlobalDraftStoragePathByKind(\n\tworkspace: string,\n\titemKind: UserDraftItemKind,\n\tpath: string\n): string {\n\treturn resolveDraftStoragePath(workspace, itemKind, path)\n}","sourceCodeStart":591,"sourceCodeEnd":627,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/global/userDraftAdapter.ts#L591-L627","documentation":"deleteGlobalDraft() performs the delete through UserDraftDbSyncer, which records failures (network errors, 5xx responses) in its state map instead of throwing synchronously. After the delete call, the adapter checks getState() and re-throws this error (preferring the syncer's own failureMessage) so callers never conclude a draft was removed when the DB-backed source of truth still contains it. The generic message is the fallback when the syncer stored no failureMessage.","triggerScenarios":"UserDraftDbSyncer.delete leaves state 'failed' — typically the underlying HTTP DELETE to the backend failed (offline, server 5xx, timeout) or the syncer rejected the request; state.failureMessage is undefined so the generic fallback message is thrown.","commonSituations":"Deleting a draft while the backend is unreachable or restarting; a proxy returning 5xx; expired session/auth causing the API call to fail; calling deleteGlobalDraft from chat tools during a network outage.","solutions":["Read state.failureMessage via UserDraftDbSyncer.getState() to see the underlying cause before retrying.","Check backend connectivity/auth, then retry deleteGlobalDraft.","Inspect the network tab for the failing DELETE request and its status code.","If failureMessage is empty, improve the syncer to always populate it so users get actionable messages."],"exampleFix":"// before\ntry { await deleteGlobalDraft(ws, 'script', p) } catch { /* assumed removed */ }\n// after\ntry {\n  await deleteGlobalDraft(ws, 'script', p)\n} catch (e) {\n  const st = UserDraftDbSyncer.getState({ workspace: ws, itemKind: 'script', path: p })\n  notify(st.failureMessage ?? String(e))\n}","handlingStrategy":"try-catch","validationCode":"const st = UserDraftDbSyncer.getState({ workspace, itemKind, path })\nif (st.state === 'failed') {\n  throw new Error(st.failureMessage ?? 'Previous sync failed; check connectivity before deleting')\n}\nif (!navigator.onLine) throw new Error('Offline: draft deletion is unavailable')","typeGuard":null,"tryCatchPattern":"try {\n  await deleteGlobalDraft(workspace, itemKind, path)\n} catch (e) {\n  const st = UserDraftDbSyncer.getState({ workspace, itemKind, path })\n  notify(st.failureMessage ?? (e instanceof Error ? e.message : 'Delete failed'))\n  // do NOT treat the draft as removed\n}","preventionTips":["Check connectivity/offline state before offering delete actions","Surface syncer failureMessage to the user rather than assuming success","Never optimistically remove the draft from local UI before confirming delete success"],"tags":["network","drafts","sync"],"backgroundTag":"draft-delete-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"}