fix(api): add retry for document blob head checks

Implement a retry mechanism for document blob HEAD requests to handle
S3 read-after-write propagation delays. This prevents 409 errors
when the client calls the endpoint immediately after performing a
direct upload to S3 via a presigned URL.
This commit is contained in:
Richard R 2026-02-16 15:51:01 -07:00
parent 596c04d4d3
commit 9931c7d0d8

View file

@ -84,19 +84,39 @@ export async function POST(req: NextRequest) {
for (const doc of documentsData) {
let headSize = doc.size;
try {
const head = await headDocumentBlob(doc.id, testNamespace);
if (head.contentLength > 0) headSize = head.contentLength;
} catch (error) {
if (isMissingBlobError(error)) {
return NextResponse.json(
{
error: `Blob missing for document ${doc.id}. Upload bytes first using /api/documents/blob/upload/presign.`,
},
{ status: 409 },
);
// Retry HEAD check to handle S3 read-after-write propagation delays.
// The client uploads bytes directly to S3 via presigned URL, then
// immediately calls this endpoint. On serverless platforms the HEAD
// request may reach S3 before the PUT is visible.
const HEAD_RETRIES = 3;
const HEAD_RETRY_DELAY_MS = 500;
let headError: unknown = null;
for (let attempt = 0; attempt < HEAD_RETRIES; attempt++) {
headError = null;
try {
const head = await headDocumentBlob(doc.id, testNamespace);
if (head.contentLength > 0) headSize = head.contentLength;
break;
} catch (error) {
if (isMissingBlobError(error)) {
headError = error;
if (attempt < HEAD_RETRIES - 1) {
await new Promise((r) => setTimeout(r, HEAD_RETRY_DELAY_MS));
continue;
}
} else {
throw error;
}
}
throw error;
}
if (headError && isMissingBlobError(headError)) {
return NextResponse.json(
{
error: `Blob missing for document ${doc.id}. Upload bytes first using /api/documents/blob/upload/presign.`,
},
{ status: 409 },
);
}
await db