---
title: Google Search Console caveats
description: What to expect from Google OAuth Testing mode, the seam's cold-start retry, final versus provisional data, and the two auth modes of the Search Console image.
---

Google Search Console is the Supported Provider the [quickstart](/tutorial/quickstart) uses. It has four properties that surprise a first-time operator. None of them is a bug in your deployment.

## the Testing-mode refresh token expires after seven days

A Google Cloud OAuth consent screen starts in **Testing** publishing status. In that status Google issues refresh tokens that expire after seven days. The token then stops working with `invalid_grant`, every scheduled tick fails at the connector's `check`, and the watermark stays where it was.

For a quickstart that is fine: create the token, run the tutorial, tear down. For a deployment that runs longer than a week, you need one of these:

- **Publish the app.** The `webmasters.readonly` scope is a sensitive scope, so publishing to **In production** requires Google's app verification. Verified apps get refresh tokens that do not expire on a timer. Plan for that review before you rely on the Cron trigger.
- **Rotate the token every seven days.** Repeat the browser consent and the `curl` exchange from the quickstart, then run `wrangler secret put GOOGLE_SEARCH_CONSOLE_SOURCE_CONFIG_JSON` again. Each `secret put` publishes a new Worker version; the next tick reads the new token. Nothing in Postgres or R2 changes, because the deployment never stores the token.

Google Workspace accounts can also use an **Internal** consent screen, which does not carry the seven-day limit and needs no verification. That option is only available when the property owner is a Workspace user.

Either way, the token is yours. Single-tenant custody (ADR 0047) means the refresh token lives only in your Worker secrets or your local Wrangler dev vars file. Do not paste it into `wrangler.jsonc`, `ads-sync.config.ts`, or a chat.

## the cold-start retry is seam behavior

The first request to a new Cloudflare Container instance can fail while the instance starts. The wrapper's `GET /metadata` answers late or with a transient error, and a naive caller marks a valid run failed before Airbyte even ran.

The Qualified Run seam owns this. `assertContainerImagePin` retries only an **unavailable** Container, up to five attempts two seconds apart (`DEFAULT_COLD_START_RETRY`), and then reads `connectorImage` and `wrapperVersion` from `/metadata` and compares them with the exact pin. A Container that answers with the wrong image or wrapper version fails at once with `metadata_mismatch`; that is never retried.

You do not configure this. Do not add your own retry loop around `POST /runs`, and do not treat one `container did not respond to /metadata after 5 attempt(s)` as a credential problem. That message means the Container never became reachable: the class is not enabled in `wrangler.jsonc`, the image did not build, or the account is not on Workers Paid.

## final data lags by three days

Search Console finalizes query and page rows two to three days after the fact. The Reference Deployment reads to the Final-Data Horizon: UTC midnight three days before the tick date (`DEFAULT_FINAL_DATA_LAG_DAYS.google_search_console`). A tick before the horizon moves records `no_new_final_data` and starts no Containers.

Provisional rows exist and are sometimes useful, but they change. Reading them is an explicit, audited mode: set the `SCHEDULE_PROVISIONAL_READS` var to the literal `"true"`. Run rows then carry `trigger_type = cron_provisional` or `manual_provisional`, and the tick ledger records `read_mode = provisional`. The default is final-data reads; keep it unless you know why you need otherwise.

The Historical Comparison Gate applies the same discipline: a Google Search Console comparison window must end at least seven days before `stableAsOf`.

## the image has two auth modes

The pinned Search Console connector advertises `access_token` in its spec but selects refresh-token OAuth unconditionally in its manifest. The shipped `Dockerfile.gsc` therefore takes a build argument:

| `GSC_AUTH_MODE` | who uses it | what the image expects |
| --- | --- | --- |
| `refresh_token` (default) | Single-tenant operators, including the quickstart | `authorization.client_id`, `client_secret`, and `refresh_token` in the source config, exactly as Google issued them. |
| `access_token` | Deployments that exchange a refresh token for a short-lived access token before dispatch | `authorization.access_token` only. `googleSearchConsoleAccessTokenSourceConfig()` builds that config. The exact-match patch `patch-gsc-manifest-auth.py` rewrites the manifest authenticator; the build fails when the pinned upstream text does not match. |

The Reference Deployment builds the default. Do not switch modes without rebuilding and redeploying the image; `/metadata` reports the selected mode as `buildOptions`.
