From 755c664e666f314c1d2e2f241231feb09bc49a2d Mon Sep 17 00:00:00 2001 From: jamiepine <32987599+jamiepine@users.noreply.github.com> Date: Sun, 4 Oct 2026 00:14:40 +0000 Subject: [PATCH] fix(history): commit generation delete cascade once, roll back on unlink failure Review follow-up: _delete_generation_children defaulted to committing the story-item and version deletes before the main audio unlink, so a locked file left the generation row in place with its story items already gone. Both single-delete and the failed-generation sweep now pass commit=False and commit once with the generation row; a failed unlink rolls back. --- backend/services/history.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/backend/services/history.py b/backend/services/history.py index 0e917626..a3a3f8ec 100644 --- a/backend/services/history.py +++ b/backend/services/history.py @@ -295,14 +295,19 @@ async def delete_generation( if not generation: return False - # Delete all version files and records - _delete_generation_children(generation_id, db) + # Delete all version files and records; commit together with the + # generation row below so an unlink failure rolls everything back. + _delete_generation_children(generation_id, db, commit=False) # Delete main audio file (if not already removed by version cleanup) if generation.audio_path: audio_path = config.resolve_storage_path(generation.audio_path) if audio_path is not None and audio_path.exists(): - audio_path.unlink() + try: + audio_path.unlink() + except OSError: + db.rollback() + raise # Delete from database db.delete(generation) @@ -325,8 +330,8 @@ async def delete_failed_generations(db: Session) -> int: failed = db.query(DBGeneration).filter(DBGeneration.status == "failed").all() count = 0 for generation in failed: - # Clean up version files/rows first. - _delete_generation_children(generation.id, db) + # Clean up version files/rows first; one commit at the end. + _delete_generation_children(generation.id, db, commit=False) # Remove the main audio file if it somehow made it to disk. if generation.audio_path: