OpenCut-app / OpenCut-app/OpenCut

[BUG] A failed storage migration permanently poisons retries and can leave saving paused

Open
#871 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
89.8k
Forks
8.9k
PR merge metrics
No merged PRs in 30d

Description

Platform

Web editor on current dev (c2e266870172312f461df75da3e7f6fbe9d2a1fc); browser-independent storage initialization failure.

Browser

All supported browsers (deterministic with a migration adapter that rejects once).

Current Behavior

A transient storage migration rejection is cached permanently at two layers, so later project loads cannot retry successfully.

On current dev:

  • ProjectManager.ensureStorageMigrations() stores storageMigrationPromise and never clears it when runStorageMigrations() rejects.
  • StorageService.ensureMigrations() likewise retains a rejected migrationsPromise.
  • ProjectManager.loadProject() calls editor.save.pause() and awaits ensureStorageMigrations() before entering its try/finally. If migration rejects, the finally that resets isLoading, notifies, and calls editor.save.resume() is skipped.
  • Subsequent loadProject()/loadAllProjects() calls await the same rejected promise rather than rerunning the migration.

After one transient IndexedDB/OPFS migration failure, project loading can remain poisoned for the manager lifetime and autosave can remain paused.

Expected Behavior

Concurrent migration callers should share one in-flight promise, successful completion should remain cached, and a rejected current promise should be cleared so the next explicit load retries. Every path after pausing save should restore loading/migration flags, notify, and resume saving exactly once. Failed initialization should not clear the currently loaded media/scenes or partially publish project state.

Recurrence Probability

Always

Steps To Reproduce
  1. Configure runStorageMigrations() to reject its first call and resolve its second.
  2. Call ProjectManager.loadProject({ id }) and observe the first rejection.
  3. Restore the migration dependency and call loadProject({ id }) or loadAllProjects() again.
  4. Observe that the original rejected storageMigrationPromise is awaited again, so migration is not retried.
  5. Observe that the first loadProject() rejected before its try/finally, leaving editor.save paused and initial loading state uncleared.
  6. Repeat at the StorageService.ensureMigrations() layer; its rejected cache behaves the same way.
Anything else?

Impact: A recoverable local-storage hiccup can make all projects unusable until the application/manager is recreated, while silently disabling further autosaves in the affected session. This is a critical availability and data-durability failure.

Bounded proposed fix: At both migration-cache layers, assign one local promise, share it across concurrent callers, cache fulfillment, and clear rejection only when the cache still points to that promise. In loadProject(), place all work after save.pause() under one try/finally; restore flags/notification and resume exactly once on every outcome. Preserve existing media/scenes and active state until migration and load succeed.

Focused acceptance tests: For both caches, rejection followed by resolution, concurrent calls invoking migration once, fulfilled-cache reuse, and identity-safe rejection cleanup. For loadProject()/loadAllProjects(), assert state preservation, loading/migration flag reset, notifications, and save pause/resume balance on every failure path.

Per the contribution policy, this report is requesting explicit maintainer approval for this candidate before any implementation or PR work begins.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with ProjectManager.ensureStorageMigrations(), ProjectManager.loadProject(), loadAllProjects(), and StorageService.ensureMigrations(); trace how their cached promises and save/loading state are handled. Run or add the focused tests described in the issue for rejection, retry, concurrency, fulfilled-cache reuse, and identity-safe cleanup. Done means failed initialization retries safely, state and flags are preserved or restored, and save pause/resume remains balanced.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.