can1357/oh-my-pi · error

Gemini Files API finalize response contains an invalid file.

Error message

Gemini Files API finalize response contains an invalid file.expirationTime

What it means

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.

Source

Thrown at packages/coding-agent/src/blob-broker/provider-files-gemini.ts:53

	}
	return value;
}

function parseFinalizedFile(payload: Record<string, unknown>): GeminiFileResource {
	const file = responseObject(payload.file, "finalize file");
	const state = requireString(file.state, "file.state");
	if (state !== "ACTIVE") throw new Error("Gemini Files API finalized file state is not ACTIVE");

	const name = requireString(file.name, "file.name");
	if (!/^files\/[^/]+$/.test(name)) {
		throw new Error("Gemini Files API finalize response contains an invalid file.name");
	}
	const uri = requireString(file.uri, "file.uri");
	const mimeType = requireString(file.mimeType, "file.mimeType");
	const expirationTime = requireString(file.expirationTime, "file.expirationTime");
	const expiresAt = Date.parse(expirationTime);
	if (!Number.isFinite(expiresAt)) {
		throw new Error("Gemini Files API finalize response contains an invalid file.expirationTime");
	}
	return { name, uri, mimeType, expiresAt };
}

function isOfficialGeminiModel(model: Model): boolean {
	if (model.provider !== "google" || model.api !== "google-generative-ai") return false;
	try {
		const baseUrl = new URL(model.baseUrl);
		return (
			baseUrl.protocol === "https:" &&
			baseUrl.hostname === "generativelanguage.googleapis.com" &&
			baseUrl.port === "" &&
			baseUrl.username === "" &&
			baseUrl.password === "" &&
			baseUrl.pathname.replace(/\/+$/, "") === "/v1beta" &&
			baseUrl.search === "" &&
			baseUrl.hash === ""
		);

View on GitHub (pinned to 9690622007)

Solutions

  1. Log the raw file.expirationTime string to see its actual format.
  2. Check whether the API version you hit renamed the field (expireTime vs expirationTime) and update the package.
  3. Convert epoch-seconds values yourself if Google switched formats, or upgrade to a version that handles it.
  4. File/report a compat issue if a Google schema change is confirmed.

Example fix

// before: trusting the timestamp format
Date.parse(file.expirationTime);
// after: normalize before parsing
const raw = file.expirationTime;
const expiresAt = /^\d+$/.test(raw) ? Number(raw) * 1000 : Date.parse(raw);
Defensive patterns

Strategy: validation

Validate before calling

null

Type guard

function isParseableTimestamp(v: unknown): v is string {
  return typeof v === "string" && Number.isFinite(Date.parse(v));
}

Try / catch

try {
  const handle = await client.upload(request);
} catch (error) {
  if (error instanceof Error && error.message.includes("invalid file.expirationTime")) {
    // log raw payload, then treat expiry as unknown: refresh via GET /v1beta/{name}
  } else throw error;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/8571751fd7154957. Report an issue: GitHub.