{"record":{"id":"ac393cf1c7eb3814","repo":"can1357/oh-my-pi","slug":"gemini-files-api-upload-finalization-request-faile","errorCode":null,"errorMessage":"Gemini Files API upload finalization request failed","messagePattern":"Gemini Files API upload finalization request failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/provider-files-gemini.ts","lineNumber":132,"sourceCode":"\n\t\t\tconst uploadUrl = startResponse.headers.get(\"X-Goog-Upload-URL\")?.trim();\n\t\t\tif (!uploadUrl)\n\t\t\t\tthrow new Error(\"Gemini Files API upload initialization response is missing X-Goog-Upload-URL\");\n\n\t\t\tlet finalizeResponse: Response;\n\t\t\ttry {\n\t\t\t\tfinalizeResponse = await fetchImpl(uploadUrl, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Length\": String(byteLength),\n\t\t\t\t\t\t\"X-Goog-Upload-Command\": \"upload, finalize\",\n\t\t\t\t\t\t\"X-Goog-Upload-Offset\": \"0\",\n\t\t\t\t\t},\n\t\t\t\t\tbody: request.bytes,\n\t\t\t\t\tsignal: request.signal,\n\t\t\t\t});\n\t\t\t} catch {\n\t\t\t\tthrow new Error(\"Gemini Files API upload finalization request failed\");\n\t\t\t}\n\t\t\tif (!finalizeResponse.ok) {\n\t\t\t\tthrow new Error(`Gemini Files API upload finalization failed with HTTP ${finalizeResponse.status}`);\n\t\t\t}\n\n\t\t\tconst file = parseFinalizedFile(await responseJson(finalizeResponse, \"finalize\"));\n\t\t\treturn {\n\t\t\t\tprovider: \"google\",\n\t\t\t\tid: file.name,\n\t\t\t\turi: file.uri,\n\t\t\t\tmimeType: file.mimeType,\n\t\t\t\tbytes: byteLength,\n\t\t\t\texpiresAt: file.expiresAt,\n\t\t\t\tdelete: {\n\t\t\t\t\tmethod: \"DELETE\",\n\t\t\t\t\turl: `${GEMINI_FILES_RESOURCE_URL}/${file.name}`,\n\t\t\t\t\theaders: { \"x-goog-api-key\": credential },\n\t\t\t\t},","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/provider-files-gemini.ts#L114-L150","documentation":"The second fetch — POST of the actual bytes to the X-Goog-Upload-URL with 'upload, finalize' — threw at the network level, so the original cause is replaced by this message. The file bytes failed to reach Google (or the connection dropped mid-transfer); HTTP error statuses produce a different message.","triggerScenarios":"fetchImpl() on the finalize request rejects: connection reset mid-upload (common with large bodies), request.signal aborted, TLS failure, DNS drop between the two requests, or body streaming failure.","commonSituations":"Uploading large files over unstable connections; timeouts/proxies killing long POSTs; user cancellation via AbortSignal; Wi-Fi/network switch during upload; request body exceeding an intermediary's size limit.","solutions":["Retry the whole upload with backoff — resumable sessions may have expired, so re-run start + finalize.","Check whether your AbortSignal fired (user cancel or your own timeout).","Reduce file size or improve connection stability; check proxy body-size/timeout limits.","For flaky networks, implement chunked resumable uploads yourself using the X-Goog-Upload-Offset protocol instead of single-shot finalize.","Note the underlying error is swallowed; wrap fetchImpl to log the native cause."],"exampleFix":"// before: single-shot, no retry\nconst handle = await client.upload({ bytes, mimeType });\n// after: retry whole resumable flow on network failure\nasync function uploadWithRetry(req, attempts = 3) {\n  try { return await client.upload(req); }\n  catch (e) {\n    if (attempts > 1 && String(e.message).includes(\"finalization request failed\")) {\n      await Bun.sleep(2000);\n      return uploadWithRetry(req, attempts - 1);\n    }\n    throw e;\n  }\n}","handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"try {\n  const handle = await client.upload(request);\n} catch (error) {\n  if (error instanceof Error && error.message.includes(\"finalization request failed\")) {\n    if (request.signal?.aborted) throw error;\n    // network drop mid-body: restart the full resumable flow with backoff\n    return uploadWithBackoff(request);\n  } else throw error;\n}","preventionTips":["Retry the entire start+finalize flow — the session URL cannot be safely resumed mid-body without offset bookkeeping","Distinguish AbortSignal cancellations from network errors before retrying","Split very large files or use chunked resumable uploads on unstable networks","Set proxy timeouts/size limits above your largest expected upload","Wrap fetchImpl to log the swallowed native error for diagnosis"],"tags":["network","upload","timeout","connection"],"backgroundTag":"upload-network-failure","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}