{"record":{"id":"479fc48983df8eb7","repo":"can1357/oh-my-pi","slug":"gemini-files-api-credential-is-required","errorCode":null,"errorMessage":"Gemini Files API credential is required","messagePattern":"Gemini Files API credential is required","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/provider-files-gemini.ts","lineNumber":87,"sourceCode":"\t\t\tbaseUrl.search === \"\" &&\n\t\t\tbaseUrl.hash === \"\"\n\t\t);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Create a native Gemini Files API client for a direct Google Generative AI model.\n * Unsupported model transports return `null` without issuing a network request.\n */\nexport function createGeminiProviderFileClient(\n\tmodel: Model,\n\tcredential: string,\n\tfetchImpl: FetchImpl = globalThis.fetch,\n): ProviderFileClient | null {\n\tif (!isOfficialGeminiModel(model)) return null;\n\tif (credential.trim().length === 0) throw new Error(\"Gemini Files API credential is required\");\n\n\treturn {\n\t\tprovider: \"google\",\n\t\tasync upload(request: ProviderFileUploadRequest): Promise<ProviderFileHandle> {\n\t\t\tconst byteLength = request.bytes.byteLength;\n\t\t\tlet startResponse: Response;\n\t\t\ttry {\n\t\t\t\tstartResponse = await fetchImpl(GEMINI_FILES_UPLOAD_URL, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\"X-Goog-Upload-Command\": \"start\",\n\t\t\t\t\t\t\"X-Goog-Upload-Header-Content-Length\": String(byteLength),\n\t\t\t\t\t\t\"X-Goog-Upload-Header-Content-Type\": request.mimeType,\n\t\t\t\t\t\t\"X-Goog-Upload-Protocol\": \"resumable\",\n\t\t\t\t\t\t\"x-goog-api-key\": credential,\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify(request.filename ? { file: { display_name: request.filename } } : { file: {} }),","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/provider-files-gemini.ts#L69-L105","documentation":"createGeminiProviderFileClient() throws synchronously when the model IS an official Google Generative AI model but the credential argument is empty or whitespace-only. The credential becomes the x-goog-api-key header on every Files API request, and uploading without it is guaranteed to fail, so the factory fails fast instead.","triggerScenarios":"Calling createGeminiProviderFileClient(model, credential) with \"\" or \"   \" while model.provider===\"google\", model.api===\"google-generative-ai\", and baseUrl is exactly https://generativelanguage.googleapis.com/v1beta.","commonSituations":"GEMINI_API_KEY / GOOGLE_API_KEY env var unset or empty; config file with a blank apikey field; credential sourced from a keychain/secret manager that returned empty; passing the wrong variable into the factory.","solutions":["Set GEMINI_API_KEY (or your configured credential source) to a valid Google AI Studio API key before constructing the client.","Trim and check the credential at the call site: if (!key?.trim()) skip or fail before calling the factory.","Verify you are passing the Google credential, not another provider's key variable.","Wrap the factory call in try-catch since it throws synchronously for official Gemini models."],"exampleFix":"// before: passing an unvalidated credential\nconst client = createGeminiProviderFileClient(model, process.env.GEMINI_API_KEY ?? \"\");\n// after: validate first\nconst key = process.env.GEMINI_API_KEY?.trim();\nif (!key) throw new Error(\"GEMINI_API_KEY is not set\");\nconst client = createGeminiProviderFileClient(model, key);","handlingStrategy":"validation","validationCode":"const key = process.env.GEMINI_API_KEY?.trim();\nif (!key) throw new Error(\"GEMINI_API_KEY is missing or empty — cannot use Gemini Files API\");","typeGuard":"function hasCredential(v: unknown): v is string {\n  return typeof v === \"string\" && v.trim().length > 0;\n}","tryCatchPattern":"try {\n  const client = createGeminiProviderFileClient(model, key);\n} catch (error) {\n  if (error instanceof Error && error.message.includes(\"credential is required\")) {\n    // fall back to inline/base64 file attachment instead of Files API upload\n  } else throw error;\n}","preventionTips":["Fail fast at startup if GEMINI_API_KEY/GOOGLE_API_KEY is unset when Google models are configured","Never default credentials to empty strings; make the key an explicit required parameter","Check secret-manager retrieval results for null/empty before use","Wrap the synchronous factory in try-catch at every call site"],"tags":["configuration","credentials","api-key"],"backgroundTag":"missing-api-key","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}