windmill-labs/windmill · error
state.failureMessage ?? `Failed to delete draft "${path}".`
Error message
state.failureMessage ?? `Failed to delete draft "${path}".` What it means
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.
Source
Thrown at frontend/src/lib/components/copilot/chat/global/userDraftAdapter.ts:609
UserDraft.remove(itemKind, storagePath, { workspace })
} else {
UserDraft.clear(itemKind, storagePath, { workspace })
}
// `remove`/`clear` only debounce the delete; persist it now so a deploy/discard
// that the caller awaits has actually cleared the server draft on return.
await UserDraftDbSyncer.save({
workspace,
itemKind,
path: storagePath,
value: null,
immediate: true
})
// A failed (network/5xx) or conflicted delete is recorded in the syncer state,
// not thrown — surface it so callers don't report the draft as removed while
// the DB-backed source of truth still has it (same guard as the write path).
const state = UserDraftDbSyncer.getState({ workspace, itemKind, path: storagePath })
if (state.state === 'failed') {
throw new Error(state.failureMessage ?? `Failed to delete draft "${path}".`)
}
if (UserDraftDbSyncer.getConflict({ workspace, itemKind, path: storagePath }).conflict) {
throw new Error(
`Draft "${path}" changed externally since you last read it; it was not removed. Re-read and retry.`
)
}
invalidateWorkspaceDrafts(workspace)
}
/** Kind-addressed live-editor storage resolution (friendly → storage path),
* for callers that must probe several draft kinds per chat type. */
export function resolveGlobalDraftStoragePathByKind(
workspace: string,
itemKind: UserDraftItemKind,
path: string
): string {
return resolveDraftStoragePath(workspace, itemKind, path)
}View on GitHub (pinned to e474e8803c)
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.
Example fix
// before
try { await deleteGlobalDraft(ws, 'script', p) } catch { /* assumed removed */ }
// after
try {
await deleteGlobalDraft(ws, 'script', p)
} catch (e) {
const st = UserDraftDbSyncer.getState({ workspace: ws, itemKind: 'script', path: p })
notify(st.failureMessage ?? String(e))
} Defensive patterns
Strategy: try-catch
Validate before calling
const st = UserDraftDbSyncer.getState({ workspace, itemKind, path })
if (st.state === 'failed') {
throw new Error(st.failureMessage ?? 'Previous sync failed; check connectivity before deleting')
}
if (!navigator.onLine) throw new Error('Offline: draft deletion is unavailable') Try / catch
try {
await deleteGlobalDraft(workspace, itemKind, path)
} catch (e) {
const st = UserDraftDbSyncer.getState({ workspace, itemKind, path })
notify(st.failureMessage ?? (e instanceof Error ? e.message : 'Delete failed'))
// do NOT treat the draft as removed
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- GET ${path} -> ${resp.status}
- Saving the latest draft failed (${failureMessage ?? 'unknown
- Could not fetch ws_specific items from server (${status ?? "
- Could not auto-fill missing lockfile entries: ${e instanceof
- ApiError with mapped HTTP status message (e.g. "Not Found",
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/534f841aec54418f.
Report an issue: GitHub.