{"record":{"id":"9969f7b57f22354d","repo":"windmill-labs/windmill","slug":"saving-the-latest-draft-failed-failuremessage","errorCode":null,"errorMessage":"Saving the latest draft failed (${failureMessage ?? 'unknown error'}). Retry before opening a session.","messagePattern":"Saving the latest draft failed \\((.+?)\\)\\. Retry before opening a session\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/sessions/pageDrawerSession.ts","lineNumber":60,"sourceCode":"// the user is looking at still holds the edit. The flush is the explicit kind, saving even\n// with auto-save off: asking for a session is asking for the edit to come along.\nasync function flushOrRefuse(query: Parameters<typeof UserDraftDbSyncer.flush>[0]): Promise<void> {\n\t// Before the save, not after: text that does not parse never reached the draft, so\n\t// leaving now would open the session on the last value that did and drop the buffer\n\t// with the drawer — and saving first would write that stale value on the way to\n\t// refusing. The editors were materialised before this call, so the check is current.\n\tif (anyEditorUnparseable()) {\n\t\tthrow new Error('This page has changes that are not valid JSON. Fix them first.')\n\t}\n\tawait UserDraftDbSyncer.flush(query)\n\tif (UserDraftDbSyncer.getConflict(query).conflict) {\n\t\tthrow new Error(\n\t\t\t'This draft has a newer conflicting version on the server. Resolve it here before opening a session.'\n\t\t)\n\t}\n\tconst { state, failureMessage } = UserDraftDbSyncer.getState(query)\n\tif (state === 'failed') {\n\t\tthrow new Error(\n\t\t\t`Saving the latest draft failed (${failureMessage ?? 'unknown error'}). Retry before opening a session.`\n\t\t)\n\t}\n}\n\n// How each page addresses a row in its hash. Resources route theirs through an extra\n// segment; every other page names the path directly.\nconst drawerHashFor = (pagePath: string, itemPath: string) =>\n\tpagePath === RESOURCES_PATH ? `/resource/${itemPath}` : itemPath\n\n/**\n * Deep-link the row whose drawer just opened, so the location says what is on screen — a\n * drawer opened from a row's Edit button is as open as one reached by link, and the chat\n * is told what the session shows through the location alone. No-op off that page, where\n * these drawers also open inside editors and pickers with no row convention to keep.\n *\n * Written straight to history rather than through the router: these pages open their\n * drawer *from* the hash, so a router-visible write would come back as a second open on","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/sessions/pageDrawerSession.ts#L42-L78","documentation":"After flushing, flushOrRefuse inspects the syncer's persist state. If state is 'failed', it throws with the syncer's failureMessage, telling the user the latest draft is not durably saved and must be successfully saved before a session can open on it.","triggerScenarios":"flushOrRefuse(query) where UserDraftDbSyncer.getState(query).state === 'failed' — the flush request to persist the draft errored (network down, 4xx/5xx, auth expired).","commonSituations":"Working offline or on a flaky connection; API returning 500 during draft save; expired session/token rejecting the draft write.","solutions":["Retry the save (fix the underlying network/auth issue) until state is success, then open the session","Check the reported failureMessage for the root cause (401 → re-login, 5xx → server logs)","Reload the page after connectivity is restored and re-flush"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const { state } = UserDraftDbSyncer.getState(query)\nif (state === 'failed') return; // don't open a session on an unsaved draft","typeGuard":"function isPersisted(state: string): boolean { return state === 'success' }","tryCatchPattern":"try { await openSession(query) } catch (e) { if (/Saving the latest draft failed/.test(String(e))) scheduleRetryFlush(); else throw e }","preventionTips":["Check sync state before navigation-gated actions","Re-authenticate promptly on 401 failures during saves","Retry flushes after connectivity is restored"],"tags":["drafts","persistence","network","sessions"],"backgroundTag":"draft-save-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"}