{"record":{"id":"672b1e7a1ee2eabe","repo":"can1357/oh-my-pi","slug":"gemini-files-api-finalized-file-state-is-not-activ","errorCode":null,"errorMessage":"Gemini Files API finalized file state is not ACTIVE","messagePattern":"Gemini Files API finalized file state is not ACTIVE","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/provider-files-gemini.ts","lineNumber":42,"sourceCode":"\ttry {\n\t\treturn responseObject((await response.json()) as unknown, context);\n\t} catch (error) {\n\t\tif (error instanceof Error && error.message.startsWith(\"Gemini Files API\")) throw error;\n\t\tthrow new Error(`Gemini Files API ${context} response is not valid JSON`);\n\t}\n}\n\nfunction requireString(value: unknown, field: string): string {\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\tthrow new Error(`Gemini Files API finalize response is missing ${field}`);\n\t}\n\treturn value;\n}\n\nfunction parseFinalizedFile(payload: Record<string, unknown>): GeminiFileResource {\n\tconst file = responseObject(payload.file, \"finalize file\");\n\tconst state = requireString(file.state, \"file.state\");\n\tif (state !== \"ACTIVE\") throw new Error(\"Gemini Files API finalized file state is not ACTIVE\");\n\n\tconst name = requireString(file.name, \"file.name\");\n\tif (!/^files\\/[^/]+$/.test(name)) {\n\t\tthrow new Error(\"Gemini Files API finalize response contains an invalid file.name\");\n\t}\n\tconst uri = requireString(file.uri, \"file.uri\");\n\tconst mimeType = requireString(file.mimeType, \"file.mimeType\");\n\tconst expirationTime = requireString(file.expirationTime, \"file.expirationTime\");\n\tconst expiresAt = Date.parse(expirationTime);\n\tif (!Number.isFinite(expiresAt)) {\n\t\tthrow new Error(\"Gemini Files API finalize response contains an invalid file.expirationTime\");\n\t}\n\treturn { name, uri, mimeType, expiresAt };\n}\n\nfunction isOfficialGeminiModel(model: Model): boolean {\n\tif (model.provider !== \"google\" || model.api !== \"google-generative-ai\") return false;\n\ttry {","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/provider-files-gemini.ts#L24-L60","documentation":"parseFinalizedFile() rejects the finalize response when file.state is a valid string but not exactly \"ACTIVE\". Gemini uploads can return states like PROCESSING, FAILED, or DISABLED; this library only supports the synchronous-finalize contract where the file is immediately usable, so any other state aborts the upload.","triggerScenarios":"The 'upload, finalize' POST returns a file whose state is \"PROCESSING\" (async processing), \"FAILED\" (Google rejected the content, e.g. unsupported/unsafe file), or \"DISABLED\" (policy-flagged).","commonSituations":"Uploading file types that Gemini processes asynchronously (video, large files); content filtered by safety/policy systems (state FAILED/DISABLED); temporary Google-side processing delays.","solutions":["Inspect the returned state value — FAILED/DISABLED means the content was rejected; pick a different file or encoding.","For PROCESSING, poll GET /v1beta/{file.name} (with x-goog-api-key) until state becomes ACTIVE, then reference it.","Verify the file's MIME type is supported by the Gemini Files API.","Retry smaller/simpler files to confirm the flow works, isolating content-specific rejection."],"exampleFix":"// before: only accepting ACTIVE synchronously\nawait client.upload(request);\n// after: poll until ACTIVE if the provider returns PROCESSING\nconst handle = await client.upload(request).catch(e => {\n  if (String(e.message).includes(\"state is not ACTIVE\")) return pollUntilActive(handleName);\n  throw e;\n});","handlingStrategy":"validation","validationCode":"const SUPPORTED = [\"image/png\", \"image/jpeg\", \"image/webp\", \"application/pdf\", \"text/plain\", \"audio/wav\", \"audio/mp3\"];\nif (!SUPPORTED.includes(request.mimeType)) {\n  throw new Error(`MIME type ${request.mimeType} likely unsupported by Gemini Files API`);\n}","typeGuard":"null","tryCatchPattern":"try {\n  const handle = await client.upload(request);\n} catch (error) {\n  if (error instanceof Error && error.message.includes(\"state is not ACTIVE\")) {\n    // file rejected or still processing: poll GET /v1beta/{name} or surface rejection to the user\n  } else throw error;\n}","preventionTips":["Only upload MIME types documented as supported by the Gemini Files API","Keep files under documented size limits (e.g. 2 GB, 20 MB per request)","Expect PROCESSING/FAILED states for video or policy-sensitive content and handle them","Screen content for safety-policy triggers before uploading"],"tags":["api-response","upload","state-validation"],"backgroundTag":"upload-not-active","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}