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

typescript
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  }}
  • code classifies the failure. See Error codes.
  • status is the HTTP status when there was one.
  • retryable says whether retrying could plausibly help.
  • details carries extra structured context. metadata.raw holds the raw response body.

Authentication and credentials

  • Both GEMINI_COOKIES and GEMINI_XSRF_TOKEN must be provided as non-empty strings when initializing the client.
  • The SDK does not load .env files automatically. Load them in your application, for example with import "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 at value from the same signed-in Gemini Web session as GEMINI_XSRF_TOKEN. Do not log or share either credential.
.env
GEMINI_COOKIES="your_complete_cookie_header"GEMINI_XSRF_TOKEN="your_at_value"
typescript
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. modelFellBack stays false.
typescript
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 GeminiRateLimitError with retryAfterMs when the backend sends a hint. Honour it.
  • A BardErrorInfo [n] marker in the body raises GeminiBardError with bardErrorCode.
  • If every request suddenly fails, Gemini may have bumped its frontend build label. Pass the new bl when required.
  • 5xx responses and socket failures raise GeminiUpstreamError; retry behavior depends on the error and configured retry policy.
typescript
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.

typescript
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 GeminiProtocolError rather than merging two answers.
  • The request timeout covers time to response headers only. A long generation is not necessarily cut short by timeoutMs; use an AbortSignal when you need a cancellation mechanism, and handle the resulting stream errors.
typescript
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 raises GeminiProtocolError.
  • Only http and https image URLs are accepted; any other scheme raises GeminiConfigurationError. Prefer https.
  • 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.toolCalls is 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 content and produces no call. Do not assume a visible block became a structured call.
  • The handoff uses the tool name and a stringified result. There is no tool_call_id field 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.
typescript
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.retryable and error.details, but never log credentials.
  • Compare metadata.requestedModel and metadata.modelId.
  • Read metadata.fallbackNotice when model identity matters.
  • Confirm the client is initialized with both cookies and xsrfToken.
  • Check that the session is current and authorized for the requested model.
  • Check finishReason before reading content.
  • Re-pass { tools } on every tool round and validate arguments.
  • If all requests fail at once, check the bl build label.