Troubleshooting
Diagnose and fix common @orcushq/gemini problems: authentication and credentials, model availability and fallback, empty responses, streaming, images, tool calling and request failures.
Start with the typed error and the response metadata. The SDK reports what the backend actually did, so most problems are visible as data rather than as a generic failure.
Read the error and the metadata first
import 'dotenv/config'import { GeminiWeb, isGeminiError } from '@orcushq/gemini' const cookies = process.env.GEMINI_COOKIESconst xsrfToken = process.env.GEMINI_XSRF_TOKEN if (!cookies?.trim() || !xsrfToken?.trim()) { throw new Error( 'GEMINI_COOKIES and GEMINI_XSRF_TOKEN are required', )} const model = new GeminiWeb({ model: 'gemini-3.6-flash', cookies, xsrfToken,}) try { const response = await model.invoke('hi') console.log(response.finishReason) console.log( response.metadata.requestedModel, response.metadata.modelId, ) console.log( response.metadata.fallbackReason, response.metadata.fallbackNotice, )} catch (error) { if (isGeminiError(error)) { console.error( error.code, error.status, error.retryable, error.details, ) } else { throw error }}codeclassifies the failure. See Error codes.statusis the HTTP status when there was one.retryablesays whether retrying could plausibly help.detailscarries extra structured context.metadata.rawholds the raw response body.
Authentication and credentials
- Both
GEMINI_COOKIESandGEMINI_XSRF_TOKENmust be provided as non-empty strings when initializing the client. - The SDK does not load
.envfiles automatically. Load them in your application, for example withimport "dotenv/config", or pass values from your secrets manager. - A non-empty cookie string does not guarantee a valid session. Sessions can expire or be revoked.
- HTTP 401 or 403 may raise
GeminiAuthenticationError. Check that the session is current and contains the cookies required by the SDK. - Use the
atvalue from the same signed-in Gemini Web session asGEMINI_XSRF_TOKEN. Do not log or share either credential.
GEMINI_COOKIES="your_complete_cookie_header"GEMINI_XSRF_TOKEN="your_at_value"import 'dotenv/config'import { GeminiWeb } from '@orcushq/gemini' const cookies = process.env.GEMINI_COOKIESconst xsrfToken = process.env.GEMINI_XSRF_TOKEN if (!cookies?.trim() || !xsrfToken?.trim()) { throw new Error( 'GEMINI_COOKIES and GEMINI_XSRF_TOKEN are required', )} const model = new GeminiWeb({ model: 'gemini-3.6-flash', cookies, xsrfToken,})Never paste credentials into a bug report
Cookies and the XSRF token are sensitive credentials. Redact them from logs, issues and screenshots, and revoke or replace the session if they are exposed.
Model availability and fallback
A successful call can still be answered by a different model. The response reports this; it is not necessarily an error. Compare requestedModel with modelId and read fallbackReason.
MODEL_REQUIRES_AUTHENTICATION: the requested model requires an authenticated session. Check that the supplied credentials are present, current and authorized for the requested model.MODEL_NOT_SUPPORTED: the id is not in the registry, so the default model may be substituted. Check for a typo or an outdated id.MODEL_UNAVAILABLE: the backend served another model for some other reason.- A missing served-model signal is not evidence of a substitution.
modelFellBackstaysfalse.
const { metadata } = await model.invoke('hi') if (metadata.fallbackNotice !== undefined) { console.warn(metadata.fallbackNotice)} if (metadata.modelFellBack) { console.warn( 'Asked for', metadata.requestedModel, 'got', metadata.modelId, )}If an id is rejected as unknown, either correct it or set unknownModel: "throw" so a typo fails loudly instead of silently using the default. See Models and Served model evidence.
Request failures and build labels
- HTTP 429 raises
GeminiRateLimitErrorwithretryAfterMswhen the backend sends a hint. Honour it. - A
BardErrorInfo [n]marker in the body raisesGeminiBardErrorwithbardErrorCode. - If every request suddenly fails, Gemini may have bumped its frontend build label. Pass the new
blwhen required. - 5xx responses and socket failures raise
GeminiUpstreamError; retry behavior depends on the error and configured retry policy.
const cookies = process.env.GEMINI_COOKIESconst xsrfToken = process.env.GEMINI_XSRF_TOKEN if (!cookies?.trim() || !xsrfToken?.trim()) { throw new Error( 'GEMINI_COOKIES and GEMINI_XSRF_TOKEN are required', )} const model = new GeminiWeb({ model: 'gemini-3.6-flash', cookies, xsrfToken, bl: 'boq_dl_...', // use the current frontend build label when needed})Empty responses
An empty answer is reported as finishReason: "empty" with empty content. It is a valid response, not a thrown error. Check the response metadata, then retry, rephrase, or switch models as appropriate.
if (response.finishReason === 'empty') { console.warn('Model returned no text; inspect metadata before retrying')}Streaming problems
- When a stream fails after emitting partial text, inspect the actual error before deciding whether to retry.
- If a retry after partially emitted text contradicts the earlier text, the SDK can raise
GeminiProtocolErrorrather than merging two answers. - The request timeout covers time to response headers only. A long generation is not necessarily cut short by
timeoutMs; use anAbortSignalwhen you need a cancellation mechanism, and handle the resulting stream errors.
const controller = new AbortController()const abortTimer = setTimeout( () => controller.abort(), 30_000,) try { const stream = await model.stream( 'Write a long report.', { signal: controller.signal }, ) for await (const chunk of stream) { process.stdout.write(chunk.content) }} catch (error) { console.error('Streaming failed or was interrupted:', error)} finally { clearTimeout(abortTimer)}Image problems
- A malformed
data:URL raisesGeminiProtocolError. - Only
httpandhttpsimage URLs are accepted; any other scheme raisesGeminiConfigurationError. Preferhttps. - Unsupported content part types raise
GeminiConfigurationError. Only text and image parts are accepted. - Remote images are fetched from your runtime. Restrict sizes with
resolveImageSource({ maxBytes })and validate untrusted URLs. - Keep one long-lived client so image upload page tokens stay cached.
Tool calling problems
response.toolCallsis populated only when tools were passed on the call. Pass{ tools }again on the follow-up.- Check
response.finishReason === "tool_calls"before executing anything. - Tool calling is prompt based. The model can ignore a tool, invent one, or return arguments that do not match the schema, so validate every call.
- A malformed tool block is removed from
contentand produces no call. Do not assume a visible block became a structured call. - The handoff uses the tool
nameand a stringified result. There is notool_call_idfield to set. - The model can request more tools after a result. Keep the history and cap the number of rounds to avoid an unbounded loop.
if (response.finishReason === 'tool_calls') { for (const call of response.toolCalls) { console.log(call.name, call.arguments) }}Full tool walkthrough
See [Tools](/docs/gemini/tools) for the complete handoff and the multi-round loop, and [Advanced recipes](/docs/gemini/recipes) for a runnable example.
Debugging checklist
- Log
error.code,error.status,error.retryableanderror.details, but never log credentials. - Compare
metadata.requestedModelandmetadata.modelId. - Read
metadata.fallbackNoticewhen model identity matters. - Confirm the client is initialized with both
cookiesandxsrfToken. - Check that the session is current and authorized for the requested model.
- Check
finishReasonbefore readingcontent. - Re-pass
{ tools }on every tool round and validate arguments. - If all requests fail at once, check the
blbuild label.