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.
This commit is contained in:
jamiepine
2026-10-04 00:19:41 +00:00
committed by capy-ai-staging[bot]
parent 8e0060cccf
commit 755c664e66
+10 -5
View File
@@ -295,14 +295,19 @@ async def delete_generation(
if not generation: if not generation:
return False return False
# Delete all version files and records # Delete all version files and records; commit together with the
_delete_generation_children(generation_id, db) # 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) # Delete main audio file (if not already removed by version cleanup)
if generation.audio_path: if generation.audio_path:
audio_path = config.resolve_storage_path(generation.audio_path) audio_path = config.resolve_storage_path(generation.audio_path)
if audio_path is not None and audio_path.exists(): 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 # Delete from database
db.delete(generation) db.delete(generation)
@@ -325,8 +330,8 @@ async def delete_failed_generations(db: Session) -> int:
failed = db.query(DBGeneration).filter(DBGeneration.status == "failed").all() failed = db.query(DBGeneration).filter(DBGeneration.status == "failed").all()
count = 0 count = 0
for generation in failed: for generation in failed:
# Clean up version files/rows first. # Clean up version files/rows first; one commit at the end.
_delete_generation_children(generation.id, db) _delete_generation_children(generation.id, db, commit=False)
# Remove the main audio file if it somehow made it to disk. # Remove the main audio file if it somehow made it to disk.
if generation.audio_path: if generation.audio_path: