From b789e186544d03f7f9c19ee2ce02a47d4254b07e Mon Sep 17 00:00:00 2001 From: Brian Le Date: Mon, 14 Sep 2026 18:59:36 -0400 Subject: [PATCH] fix: send x-upsert on resumable upload PATCH/HEAD so reprocess can overwrite processed.mp4 Supabase evaluates upsert when the TUS upload is finalized, which is the PATCH that completes the object, not the create request. Reprocessing any recording whose processed.mp4 already existed failed with 409 Conflict on production (match e6ba4268, 2026-09-14) and was marked failed with 'The analyzed video could not be saved.' --- backend/pipeline/storage.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/pipeline/storage.py b/backend/pipeline/storage.py index 24309c6e..553b8b2a 100644 --- a/backend/pipeline/storage.py +++ b/backend/pipeline/storage.py @@ -161,7 +161,7 @@ def _upload_with_retry(upload_fn, label: str, deadline: float): def _tus_offset(client: httpx.Client, location: str, auth: dict, timeout: float) -> int: """Return the server's current durable offset for a TUS upload (to resume).""" - resp = client.head(location, headers={**auth, "Tus-Resumable": "1.0.0"}, timeout=timeout) + resp = client.head(location, headers={**auth, "Tus-Resumable": "1.0.0", "x-upsert": "true"}, timeout=timeout) resp.raise_for_status() return int(resp.headers["Upload-Offset"]) @@ -234,6 +234,11 @@ def _create() -> str: "Tus-Resumable": "1.0.0", "Upload-Offset": str(offset), "Content-Type": "application/offset+octet-stream", + # Supabase evaluates upsert when the object is finalized, i.e. on the + # PATCH that completes the upload. Without it here, reprocessing a + # recording whose processed.mp4 already exists fails with 409 Conflict + # (observed on production 2026-09-14, match e6ba4268). + "x-upsert": "true", }, content=chunk, timeout=_timeout(),