{"record":{"id":"8571751fd7154957","repo":"can1357/oh-my-pi","slug":"gemini-files-api-finalize-response-contains-an-inv-857175","errorCode":null,"errorMessage":"Gemini Files API finalize response contains an invalid file.expirationTime","messagePattern":"Gemini Files API finalize response contains an invalid file\\.expirationTime","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/provider-files-gemini.ts","lineNumber":53,"sourceCode":"\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 {\n\t\tconst baseUrl = new URL(model.baseUrl);\n\t\treturn (\n\t\t\tbaseUrl.protocol === \"https:\" &&\n\t\t\tbaseUrl.hostname === \"generativelanguage.googleapis.com\" &&\n\t\t\tbaseUrl.port === \"\" &&\n\t\t\tbaseUrl.username === \"\" &&\n\t\t\tbaseUrl.password === \"\" &&\n\t\t\tbaseUrl.pathname.replace(/\\/+$/, \"\") === \"/v1beta\" &&\n\t\t\tbaseUrl.search === \"\" &&\n\t\t\tbaseUrl.hash === \"\"\n\t\t);","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/provider-files-gemini.ts#L35-L71","documentation":"parseFinalizedFile() throws this when file.expirationTime is a non-empty string but Date.parse() returns NaN, i.e. it is not a parseable RFC 3339 timestamp. The library converts it to a numeric expiresAt epoch for the handle, so an unparseable timestamp cannot be represented.","triggerScenarios":"Google's finalize response supplies file.expirationTime in an unexpected format (locale date, epoch seconds instead of ISO string, or a renamed field leaving a stale value).","commonSituations":"API version drift changing the timestamp format; field renamed to e.g. expireTime in newer API revisions so the parsed value is undefined/empty; response tampering by middleware.","solutions":["Log the raw file.expirationTime string to see its actual format.","Check whether the API version you hit renamed the field (expireTime vs expirationTime) and update the package.","Convert epoch-seconds values yourself if Google switched formats, or upgrade to a version that handles it.","File/report a compat issue if a Google schema change is confirmed."],"exampleFix":"// before: trusting the timestamp format\nDate.parse(file.expirationTime);\n// after: normalize before parsing\nconst raw = file.expirationTime;\nconst expiresAt = /^\\d+$/.test(raw) ? Number(raw) * 1000 : Date.parse(raw);","handlingStrategy":"validation","validationCode":"null","typeGuard":"function isParseableTimestamp(v: unknown): v is string {\n  return typeof v === \"string\" && Number.isFinite(Date.parse(v));\n}","tryCatchPattern":"try {\n  const handle = await client.upload(request);\n} catch (error) {\n  if (error instanceof Error && error.message.includes(\"invalid file.expirationTime\")) {\n    // log raw payload, then treat expiry as unknown: refresh via GET /v1beta/{name}\n  } else throw error;\n}","preventionTips":["Check raw expirationTime values when upgrading API versions (field renames are common)","Keep fixtures in tests using RFC 3339 timestamps exactly as Google returns them","Track Google's field naming (expirationTime vs expireTime) across API revisions","Pin the package version alongside the API version you validated against"],"tags":["validation","date-parsing","api-response"],"backgroundTag":"invalid-timestamp-format","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}