Skip to main content
Read this before running a deep analysis on an account you have never touched. It is the runbook from real engagements, expanded to the steps, the gates between them, and what “good” looks like at each gate. The order matters: each phase establishes facts that can invalidate the next. Skipping Phase 0 is how confident, wrong, statistically significant answers get produced. The same runbook is available mid-session as the resource aga://guides/first-analysis, and the gsc-content-deep-dive prompt walks an agent through it.

Phase 0 — Site structure (do not skip)

Everything here is cheap to check and expensive to miss.
  • Call gsc_get_context FIRST — before any performance call. Record the publishing base, CMS URL, reverseProxy, matched property set, first publish date, maturity block, and placeholder flag. A reverse-proxy rewrite or migration splits the dataset: every cohort spanning it is invalid. Expect undiscovered migrations here — a proxy change discovered late invalidates every cohort definition built before the discovery. The placeholder flag matters too: accounts still on the shared placeholder space will never match a GSC property, and the performance tools refuse to pull until the real domain is set.
  • Call gsc_list_sites — confirm which GSC properties aggregate.
  • Enumerate EVERY hostname in a page-dimension export (rows carry tags.hostname / tags.pageClass). Legacy subdomains hide here — it is common to find a forgotten blog. subdomain quietly holding a large share of an intent class at deep positions.
  • Identify migrations, replatforms, proxy changes. Each is a structural break; cohorts split there.
  • Ask the client: what bulk operations ran, and when? What does the content itself record (bylines, modified-time meta, schema.org)?
What good looks like: you can name every hostname in the property, every structural break date, and every bulk operation — before you have pulled a single performance row. Common first-timer mistake: building cohorts from publish dates alone and discovering the migration after the event study is written.

Phase 1 — Acquisition, on a budget

Google enforces a daily per-property load quota. When it trips, EVERY call fails until it resets the next day — narrower date ranges and tiny row limits fail identically, because it is a property-level daily cap, not a request-size limit. Enumerate the pulls you need before you start.
  1. Size first. Call gsc_query_analytics before exporting — it is cheap (totals plus top rows) and tells you whether the range fits.
  2. Export [page, date] in monthly slices; verify truncatedByApi: false on each. The cap is 25,000 rows per property per call, and a single busy month on a growing site can approach it. Auto-partitioning rescues capped pulls, but then you must verify coverageRatio of at least 0.98 — avoiding the cap is cleaner.
  3. Export [query, date] the same way.
  4. Pull [page, query] EARLY for a recent window and read meta.anonymization — it reports what fraction of impressions and clicks sits on named queries. Do not leave this for later: this block has retracted headline findings after they were already reported. See GSC data fidelity.
  5. Download every export immediately. The file lives on the MCP server, not your machine — a local read of the returned filePath fails. The response carries a pre-signed downloadUrl that expires in about an hour:
    Do not collect URLs and fetch hours later.
  6. Pull the content corpus with articles_search (statuses ["published"]): slugs, publishDate, updatedAt.
  7. Pull the content planner per mode, with an explicit accountId — the session’s current account is an in-memory override that can reset silently, subagents especially. Use the returned rows, not questionCounts.total, which can undercount badly — the returned rows are authoritative.
  8. Prefer one wide export plus local re-slicing (gsc_query_export, or pandas over the downloaded JSON) over many narrow API calls. Identical params reuse the 6-hour cache.
What good looks like: a written pull list before the first export, every export under the cap with truncatedByApi: false, and every file downloaded within the hour. Common first-timer mistake: iterating exploratorily against the API until the quota trips mid-analysis, then losing the afternoon.

Phase 2 — Panel construction

  • Normalize URLs: strip fragments (GSC reports table-of-contents anchors as distinct URLs — thousands of rows may be fragment duplicates that fold back into parent slugs), fold www/non-www, trailing slashes, and query strings.
  • Audit every substring path filter. A filter like page contains "articles" can also match unrelated URLs that happen to contain the string. Enumerate what the filter caught; exclude explicitly and record the impression cost.
  • Cut the panel at today − 3 days; GSC data settles over about 3 days. meta.incompleteDays flags only dates inside that real lag window — a months-old export carries no flags.
  • Reconcile. Sum the merged exports and compare against a single gsc_query_analytics call over the same range. Do not proceed until they match; after normalization, the panel should differ only by your deliberate exclusions. This gate catches filter mistakes, overlapping date ranges, and silent truncation.
  • Write canonical intermediates — one page panel, one query panel. Everything downstream reads these, so a normalization fix propagates once.
What good looks like: merged totals reconcile exactly, exclusions are itemized with their impression cost, and one canonical panel file is the single input to every later step.

Phase 3 — Ground truth

  • Validate publishDate: does any page show impressions before its recorded publish date? If none do, the field can be trusted.
  • Test whether updatedAt is real or a bulk touch: count distinct dates and the max articles sharing one. Hundreds of articles sharing a handful of timestamps means a bulk operation, not update history.
  • If updatedAt is dead, look for the true date IN THE CONTENT: the true dates may exist only in the content itself — bylines, schema.org blocks, article:modified_time. Ask the client what their content contains — in practice this field is recovered because the client mentions it, not because analysis finds it.
  • Compute the instrument’s coverage PER PERIOD (named-query share of impressions) and check it is stable — the named share can double within months as a site grows, and distinct-query counts are not comparable across those months.
  • Validate every proxy against ground truth on a sample before it carries an analysis; report the mis-assignment rate. Unvalidated proxies have mis-assigned nearly an entire cohort and silently dropped a large share of a corpus from every denominator.
What good looks like: every date field in the panel is either validated against the data or labelled as a measured proxy with its error rate.

Phase 4 — Analysis

Split cohorts at every structural break; de-trend every age curve; use balanced panels and report N at every point; run placebos before believing any event study; size segments at page level. The full rule set is the companion guide Analysis guardrails — read it before modelling. It exists because each rule, broken once, produced a confident wrong answer.

Phase 5 — Reporting

  • Quote bounds, not point estimates, wherever the instrument is biased — page-level ceiling vs named-query floor. The page-level ceiling can be several times the named-query floor; the truth is between, and the bound is the finding.
  • State explicitly what each result does NOT establish.
  • Keep discarded estimators documented so they stay discarded — both the dead estimator and the one that replaced it belong in the record.
  • List the assumptions unverifiable from data, and name who can verify them.
The bias rules behind these are in GSC data fidelity.

See also