BUG: deleteObject in asset hard-delete may throw — route has no catch per-item #62

Closed
opened 2026-05-25 02:59:36 -04:00 by zgaetano · 0 comments
Owner

Bug

DELETE /api/v1/assets/:id?hard=true calls deleteObject(proxy_s3_key), deleteObject(thumbnail_s3_key), deleteObject(original_s3_key) sequentially. If any one of these throws (S3 unavailable, bucket missing, key not found), the entire request fails 500 — but the previous S3 objects may already have been deleted. The asset row still exists because the SQL DELETE hasn't executed yet.

Impact

  • Partial delete: some S3 objects gone, asset row still in DB
  • Re-trying the DELETE will fail on the already-deleted S3 objects (S3 NoSuchKey throws unless DeleteObjectCommand is idempotent — need to verify)
  • Orphaned asset row with dangling keys

Location

services/mam-api/src/routes/assets.js:295-301

if (asset.proxy_s3_key)     await deleteObject(asset.proxy_s3_key);
if (asset.thumbnail_s3_key) await deleteObject(asset.thumbnail_s3_key);
if (asset.original_s3_key)  await deleteObject(asset.original_s3_key);
await pool.query('DELETE FROM assets WHERE id = $1', [id]);
  • Wrap each delete in try/catch (log + continue) — S3 deletion is best-effort during cleanup
  • Or delete the DB row FIRST, then best-effort clean S3 objects
## Bug `DELETE /api/v1/assets/:id?hard=true` calls `deleteObject(proxy_s3_key)`, `deleteObject(thumbnail_s3_key)`, `deleteObject(original_s3_key)` sequentially. If any one of these throws (S3 unavailable, bucket missing, key not found), the *entire* request fails 500 — but the previous S3 objects may already have been deleted. The asset row still exists because the SQL DELETE hasn't executed yet. ## Impact - Partial delete: some S3 objects gone, asset row still in DB - Re-trying the DELETE will fail on the already-deleted S3 objects (S3 `NoSuchKey` throws unless `DeleteObjectCommand` is idempotent — need to verify) - Orphaned asset row with dangling keys ## Location `services/mam-api/src/routes/assets.js:295-301` ```js if (asset.proxy_s3_key) await deleteObject(asset.proxy_s3_key); if (asset.thumbnail_s3_key) await deleteObject(asset.thumbnail_s3_key); if (asset.original_s3_key) await deleteObject(asset.original_s3_key); await pool.query('DELETE FROM assets WHERE id = $1', [id]); ``` ## Recommended - Wrap each delete in try/catch (log + continue) — S3 deletion is best-effort during cleanup - Or delete the DB row FIRST, then best-effort clean S3 objects
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: WildDragonLLC/dragonflight#62
No description provided.