Comfy-Org / Comfy-Org/ComfyUI_frontend
Workflow draft autosave can permanently fail (QuotaExceededError) with no recovery except manual localStorage clear
- Dominant language
- TypeScript
- Stars
- 2k
- Forks
- 699
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 490
Description
## Summary
The "workflow draft could not be saved" toast (`workflowDraftV2` module in `settingStore-*.js`) can get permanently stuck for a browser origin even after a hard refresh (Ctrl+Shift+R), and the only fix users currently have is manually clearing `localStorage` from DevTools. This traces to `handleQuotaExceeded()` reaching `markStorageUnavailable()` when the draft *index* is already fully evicted, which means something else in the same origin's `localStorage` is consuming the quota — not the draft mechanism itself, which is otherwise self-limiting.
## What I verified by reading the shipped bundle (`comfyui_frontend_package`, `settingStore-*.js`)
- `getWorkspaceId()` reads `sessionStorage['Comfy.Workspace.Current']` and defaults to `'personal'` when absent — this is scoped to ComfyUI's multi-user/workspace feature, not something a typical single-user local install touches.
- `saveDraft()` → `upsertEntry(index, path, entry, 32)` caps the draft index at **32 entries per workspace**, evicting the least-recently-touched entry (`touchOrder`) and calling `deletePayloads()` for every evicted key. This path is self-limiting and doesn't leak: evicted index entries and their `Comfy.Workflow.Draft.v2:*` payloads are removed together.
- On `QuotaExceededError`, `handleQuotaExceeded()` evicts entries **one at a time, oldest first, down to zero**, retrying the write after each eviction. Only if the write *still* throws `QuotaExceededError` after the entire index (all 32 possible entries) has been evicted does it call `markStorageUnavailable()`.
- `markStorageUnavailable()` only flips an **in-memory** module-scope flag (`ss = false`); it is not persisted to storage and is reset on every full page load. So a `QuotaExceededError` that recurs identically after every fresh page load — appearing "stuck" to the user — can only happen if the write keeps failing for a reason **unrelated to the draft index itself** (which, per the point above, is emptied before `markStorageUnavailable()` is ever reached).
**Conclusion: the origin's `localStorage` (5–10 MB typical quota) is being filled by something other than `Comfy.Workflow.Draft.v2:*` / `Comfy.Workflow.DraftIndex.v2:*` keys.** Two candidates visible in the same module:
- `localStorage['workflow']` — a full, uncapped serialized-graph autosave used as a fallback in `loadPersistedWorkflow()`. No size cap is applied to it in the code I read.
- Individual draft **payloads** (`Comfy.Workflow.Draft.v2:*`) also have no per-entry size cap — a handful of large/complex graphs (many nodes, embedded base64 image/prompt data) can fill the whole quota well before the 32-entry count limit is ever reached, so the eviction-by-count logic doesn't help.
I could not inspect a live browser's actual `localStorage` contents from this environment (no repro browser session on hand), so I can't name the exact key(s) responsible with certainty — but the mechanism above rules out a code-level leak in the draft/eviction logic itself and points at oversized values for one or a few keys in this origin.
## Repro / how a user hits the permanently-stuck state
1. Work in ComfyUI long enough (many large workflow tabs, complex graphs with embedded images) that the origin's `localStorage` fills close to the browser's 5–10 MB per-origin quota.
2. `saveDraft()` throws `QuotaExceededError`; `handleQuotaExceeded()` evicts all 32 possible draft entries but the write still fails (because the quota pressure is external to the draft keys) → `markStorageUnavailable()`.
3. User hard-refreshes. `ss` resets to `true` in the new page's module scope, so the *next* `saveDraft()` call is attempted again — and immediately fails the same way, since the underlying oversized key(s) are still sitting in localStorage. The user perceives this as "a hard refresh doesn't fix it."
## Suggested improvements
- Emit a distinguishable console warning (or richer toast detail) when `markStorageUnavailable()` fires *after* the index was already empty pre-eviction — that's the specific signal that the problem is NOT the draft mechanism, and today it's indistinguishable from "still evicting normally."
- Consider capping `Comfy.Workflow.Draft.v2:*` payload size (skip persisting a draft above N KB rather than let it silently starve the whole quota for everything else in the origin).
- Consider capping/removing the uncapped `localStorage['workflow']` fallback autosave, or at least logging its size when quota errors occur, so users/devs can identify it as the culprit without manually walking `Object.entries(localStorage)`.
## Workaround given to the user in the meantime
In DevTools console, find the actual largest key(s) in the origin before blindly wiping everything:
```js
Object.entries(localStorage).map(([k,v]) => [k, v.length]).sort((a,b) => b[1]-a[1]).slice(0, 20)
```
Then clear the draft/workflow-state prefixes:
```js
Object.keys(localStorage)
.filter(k => k.startsWith('Comfy.Workflow.Draft.v2:') ||
k.startsWith('Comfy.Workflow.DraftIndex.v2:') ||
k.startsWith('Comfy.Workflow.ActivePath:') ||
k.startsWith('Comfy.Workflow.OpenPaths:') ||
k.startsWith('Comfy.Workflow.LastActivePath:') ||
k.startsWith('Comfy.Workflow.LastOpenPaths:') ||
k === 'workflow')
.forEach(k => localStorage.removeItem(k));
```
## Environment
- `comfyui_frontend_package` (bundle inspected: `settingStore-JJ6taRxG.js`)
- Reported from a local single-user install with a large, long-running workflow library (150+ complex production graphs, some with embedded base64 image data)
Contributor guide
Research direction
Start with the shipped settingStore-JJ6taRxG.js bundle and trace saveDraft(), handleQuotaExceeded(), and markStorageUnavailable(). Inspect the localStorage keys involved, especially workflow and the Comfy.Workflow.Draft.v2:* and Comfy.Workflow.DraftIndex.v2:* prefixes, while reproducing quota exhaustion if possible. Done means the chosen diagnostic or recovery improvement distinguishes external quota pressure from normal draft eviction and is verified against the reported hard-refresh scenario.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100