[BUG] Project rename/delete races can lose data and report failed persistence as success
Platform
Web editor using the browser storage adapters (OPFS and IndexedDB), current official dev at c2e266870172312f461df75da3e7f6fbe9d2a1fc.
Browser
Any supported browser; reproduced deterministically with delayed/rejecting storage-adapter promises.
Current Behavior
Project rename and deletion are not ordered with autosave or with each other, and their public methods do not expose failures to callers.
Current dev evidence:
SaveManager.saveNow() calls ProjectManager.saveCurrentProject() while tracking only a process-wide isSaving flag (apps/web/src/core/managers/save-manager.ts).
ProjectManager.saveCurrentProject() snapshots this.active, awaits storageService.saveProject({ project: updatedProject }), then assigns that captured snapshot back to this.active and metadata (project-manager.ts, saveCurrentProject).
ProjectManager.renameProject() independently reloads the project, changes name/updatedAt, saves it, and then publishes it (project-manager.ts, renameProject). There is no shared per-project fence or generation/currentness check between these operations.
ProjectManager.deleteProjects() runs storageService.deleteProjectMedia() and storageService.deleteProject() concurrently for each ID with Promise.all. A failure can therefore leave either the project record or its media behind without a deterministic phase result.
- Both
renameProject() and deleteProjects() catch persistence errors internally and resolve Promise<void>. Consequently, apps/web/src/app/projects/page.tsx clears selection and closes delete/rename dialogs after an unsuccessful operation. apps/web/src/components/editor/editor-header.tsx can also route to /projects after a failed active-project deletion because its catch never receives the swallowed error.
A delayed pre-rename autosave can overwrite and republish the old name after rename reports success. A delete can remove the record while media deletion fails (or remove media while record deletion fails), while the UI cannot distinguish confirmed deletions from failures.
Expected Behavior
Rename and delete should use the same per-project persistence coordinator introduced by the save-durability work, with deterministic outcomes:
- Rename acquires an exclusive project fence before awaiting, drains older saves, derives from the current canonical project (or a fresh durable load), changes only
name and updatedAt, and publishes only after a successful, still-current commit.
- Delete sorts and deduplicates IDs, closes admission before awaiting, drains registered project work, then deletes each target in a fixed order: media bytes, media metadata, project record.
- Each delete result identifies the project ID, failed phase, error/attempt count, and whether record deletion was confirmed. Confirmed record deletion leaves a coordinator tombstone so late saves/renames cannot recreate the project.
- A pre-record failure reconciles durable state before reopening admission. Mixed batches remove only confirmed IDs from memory/selection and allow retry of failed IDs.
- Manager methods return/throw typed outcomes; one caller owns the toast and changes dialogs, selection, or route only for confirmed success.
This should consume the shared coordinator/fence from the separate save-durability candidate rather than introducing a second mutex or caller-only wait.
Recurrence Probability
Always, when the relevant adapter promises are delayed or rejected in the described order.
Steps To Reproduce
Rename overwritten by an older autosave
- Open project A named
Original and make a timeline change so SaveManager starts saveCurrentProject().
- Delay that autosave's
storageService.saveProject() after it captures the project snapshot.
- Rename A to
Renamed and allow renameProject()'s load/save to complete.
- Release the older autosave.
- Inspect the active project, projects metadata, raw stored project, and a fresh-manager reload.
- Observe that the older snapshot can restore/persist
Original even though rename already reported success.
Partial deletion reported as success
- Create a project with at least one persisted media asset.
- Make
deleteProjectMedia({ projectId }) reject while deleteProject({ id }) succeeds (repeat with the opposite failure).
- Delete from the projects page or editor header.
- Inspect project metadata, raw project/media stores, selection/dialog state, route, and a fresh-manager reload.
- Observe a half-deleted project and that
deleteProjects() resolves after logging the failure, allowing the caller to present success behavior.
Anything else?
Impact
This can silently undo a successful rename, resurrect stale active state, orphan project/media data, or navigate/clear selection after persistence failed. Retrying without a durable deletion fence can race another autosave and recreate a deleted project.
Bounded proposed fix
Limit this issue to project record mutation ordering and typed caller outcomes in ProjectManager, storage deletion helpers, the shared coordinator, and the projects/editor mutation controllers. Media-operation FIFO and object-URL reconciliation remain separate follow-up work; delete should expose the registration/drain boundary that follow-up can extend.
Focused acceptance tests
- Deferred autosave followed by rename: durable reload keeps the new name and newer timeline state in both completion orders.
- Save rejection before rename; dirty work during rename; rename failure leaves memory unchanged and yields one caller toast.
- Active delete and projects-page delete while save/close work is pending.
- Post-tombstone late save and rename never reach storage adapters or recreate the record.
- Bytes, media-metadata, and project-record deletion failures each return the exact typed phase and preserve retryable UI state.
- Two-ID mixed batch removes/routes only confirmed IDs, retains failed selection, and retry completes only failed IDs.
- Fresh manager confirms successful deletion is absent and pre-record failure is reconciled.
Duplicate search
Searched all OpenCut issues for rename delete project, project deletion storage, project rename, data loss rename, autosave delete project, and delete project failure. No matching issue was found. The closest results are unrelated: #413 (https://github.com/OpenCut-app/OpenCut/issues/413) requested the editor rename UI and is closed; #192 (https://github.com/OpenCut-app/OpenCut/issues/192) concerns the OpenCut product name/trademark.
Because the contribution guide accepts critical fixes only case by case, please confirm candidate-specific maintainer approval and the shared-coordinator ownership before implementation begins.
[BUG] Project rename/delete races can lose data and report failed persistence as success
Platform
Web editor using the browser storage adapters (OPFS and IndexedDB), current official
devatc2e266870172312f461df75da3e7f6fbe9d2a1fc.Browser
Any supported browser; reproduced deterministically with delayed/rejecting storage-adapter promises.
Current Behavior
Project rename and deletion are not ordered with autosave or with each other, and their public methods do not expose failures to callers.
Current
devevidence:SaveManager.saveNow()callsProjectManager.saveCurrentProject()while tracking only a process-wideisSavingflag (apps/web/src/core/managers/save-manager.ts).ProjectManager.saveCurrentProject()snapshotsthis.active, awaitsstorageService.saveProject({ project: updatedProject }), then assigns that captured snapshot back tothis.activeand metadata (project-manager.ts,saveCurrentProject).ProjectManager.renameProject()independently reloads the project, changesname/updatedAt, saves it, and then publishes it (project-manager.ts,renameProject). There is no shared per-project fence or generation/currentness check between these operations.ProjectManager.deleteProjects()runsstorageService.deleteProjectMedia()andstorageService.deleteProject()concurrently for each ID withPromise.all. A failure can therefore leave either the project record or its media behind without a deterministic phase result.renameProject()anddeleteProjects()catch persistence errors internally and resolvePromise<void>. Consequently,apps/web/src/app/projects/page.tsxclears selection and closes delete/rename dialogs after an unsuccessful operation.apps/web/src/components/editor/editor-header.tsxcan also route to/projectsafter a failed active-project deletion because itscatchnever receives the swallowed error.A delayed pre-rename autosave can overwrite and republish the old name after rename reports success. A delete can remove the record while media deletion fails (or remove media while record deletion fails), while the UI cannot distinguish confirmed deletions from failures.
Expected Behavior
Rename and delete should use the same per-project persistence coordinator introduced by the save-durability work, with deterministic outcomes:
nameandupdatedAt, and publishes only after a successful, still-current commit.This should consume the shared coordinator/fence from the separate save-durability candidate rather than introducing a second mutex or caller-only wait.
Recurrence Probability
Always, when the relevant adapter promises are delayed or rejected in the described order.
Steps To Reproduce
Rename overwritten by an older autosave
Originaland make a timeline change soSaveManagerstartssaveCurrentProject().storageService.saveProject()after it captures the project snapshot.Renamedand allowrenameProject()'s load/save to complete.Originaleven though rename already reported success.Partial deletion reported as success
deleteProjectMedia({ projectId })reject whiledeleteProject({ id })succeeds (repeat with the opposite failure).deleteProjects()resolves after logging the failure, allowing the caller to present success behavior.Anything else?
Impact
This can silently undo a successful rename, resurrect stale active state, orphan project/media data, or navigate/clear selection after persistence failed. Retrying without a durable deletion fence can race another autosave and recreate a deleted project.
Bounded proposed fix
Limit this issue to project record mutation ordering and typed caller outcomes in
ProjectManager, storage deletion helpers, the shared coordinator, and the projects/editor mutation controllers. Media-operation FIFO and object-URL reconciliation remain separate follow-up work; delete should expose the registration/drain boundary that follow-up can extend.Focused acceptance tests
Duplicate search
Searched all OpenCut issues for
rename delete project,project deletion storage,project rename,data loss rename,autosave delete project, anddelete project failure. No matching issue was found. The closest results are unrelated: #413 (https://github.com/OpenCut-app/OpenCut/issues/413) requested the editor rename UI and is closed; #192 (https://github.com/OpenCut-app/OpenCut/issues/192) concerns the OpenCut product name/trademark.Because the contribution guide accepts critical fixes only case by case, please confirm candidate-specific maintainer approval and the shared-coordinator ownership before implementation begins.