> ## Documentation Index
> Fetch the complete documentation index at: https://subtext.fullstory.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# GrowthBook and Subtext: From an Experiment Result to the Session

> Attach the Subtext session URL to your GrowthBook exposures, then jump from a variation that moved a metric straight into the real session of a user who saw it.

GrowthBook tells you *that* a variation moved a metric: a feature flag rolled out, an experiment reached significance, a variation lost on conversion. It can't show you *what* a user in that variation actually experienced. Subtext can. GrowthBook is warehouse-native, so it routes every experiment exposure through your `trackingCallback` — attach the Subtext session URL there and each exposure carries a link back to the real session. Your agent reads the `subtext_url`, opens that person's session, and walks the variation experience with screenshots, the component tree, network, and console. GrowthBook detects; Subtext diagnoses.

<Note>
  This guide assumes the [Subtext capture snippet](/docs/install/overview) is installed and capturing sessions, and that the GrowthBook SDK (`@growthbook/growthbook`) is initialized with a `trackingCallback`.
</Note>

## Attach the session URL

GrowthBook doesn't store user data itself — attributes are evaluated locally, and only your `trackingCallback` persists exposures. So attach the URL in two places: on the attributes (so it's available to evaluate against) and through the tracking callback (so it lands in your exposure stream).

### On the GrowthBook attributes

Attach the URL to the GrowthBook attributes with `setAttributes`. Spread the existing attributes first so you don't wipe targeting fields. In React, put it in the same `useEffect` as `FS('setIdentity')`.

<CodeGroup>
  ```js analytics.js theme={null}
  const subtextUrl = FS('getSession', { format: 'url.now' })
  if (subtextUrl) {
    gb.setAttributes({ ...gb.getAttributes(), subtext_url: subtextUrl })
  }
  ```

  ```tsx React theme={null}
  useEffect(() => {
    if (!user) return
    FS('setIdentity', { uid: user.id, properties: { email: user.email } })
    const subtextUrl = FS('getSession', { format: 'url.now' })
    if (subtextUrl) {
      gb.setAttributes({ ...gb.getAttributes(), id: user.id, subtext_url: subtextUrl })
    }
  }, [user])
  ```
</CodeGroup>

### Through the tracking callback

GrowthBook routes experiment exposures through `trackingCallback`. Read the attribute there so the URL lands in your warehouse exposure table alongside the experiment and variation.

```js theme={null}
const gb = new GrowthBook({
  trackingCallback: (experiment, result) => {
    analytics.track('experiment_viewed', {
      experimentId: experiment.key,
      variationId: result.key,
      subtext_url: gb.getAttributes().subtext_url,
    })
  },
})
```

<Warning>
  Don't read `FS('getSession')` at module top level — it returns `null` until the session has started. Don't pass a bare object to `setAttributes` — it replaces all attributes, so spread `gb.getAttributes()` first. And don't expect the URL to appear anywhere without a `trackingCallback` — GrowthBook evaluates locally and only your callback persists exposures. The URL changes on a new session and when `FS('setIdentity')` is called with a different `uid`, so re-call `setAttributes` whenever you re-identify the user; attributes are frozen until the next call.
</Warning>

## From signal to session

However you first hear that a variation moved — a significance alert, a results review, a health warning — the path is the same: get to the `subtext_url` for a few exposed users, then hand them to your agent for a Detect-vs-Diagnose read. The result says the variation regressed; the session shows whether it rendered correctly, errored, or simply behaved in a way users disliked.

<AccordionGroup>
  <Accordion title="A significance alert in Slack">
    GrowthBook posts to Slack: *"Experiment `pricing-table-v2` reached significance — variation B is down on `signup_rate`."*

    Pull the regressed users' exposures from where your tracking callback writes them:

    ```sql theme={null}
    SELECT user_id, experiment_id, variation_id, subtext_url, timestamp
    FROM experiment_viewed
    WHERE experiment_id = 'pricing-table-v2'
      AND variation_id = 'B'
      AND subtext_url IS NOT NULL
    ORDER BY timestamp DESC
    LIMIT 20;
    ```

    Copy a few `subtext_url` values, then hand them to your agent:

    ```text theme={null}
    GrowthBook says variation B of pricing-table-v2 reached significance and lost on signup_rate.
    Here are sessions from users who were exposed to variation B:
    <PASTE 3-4 subtext_urls>

    Detect-vs-Diagnose: the result says B is worse — what does B actually look like for these users?
    Did the variation render correctly, throw an error, or just read differently? One root-cause
    hypothesis and a ship / iterate / kill recommendation.
    ```
  </Accordion>

  <Accordion title="An experiment review meeting">
    In the experiment review, a variation's lift is negative and the room wants to know whether it's the design or a bug.

    From the `experiment_viewed` event in your analytics tool (PostHog / Amplitude / Mixpanel), open the **Properties** tab and copy `subtext_url` for a few users in the losing variation.

    ```text theme={null}
    We're reviewing <experiment>. Variation <X> has negative lift. Here's a session from a user who
    saw it: <PASTE subtext_url>

    Open it and tell me what the variation experience was actually like. Is the lift loss explained by
    a rendering bug / error, or did the design just underperform? I need a concrete cause for the
    review, not speculation.
    ```
  </Accordion>

  <Accordion title="A health warning (SRM or anomaly)">
    GrowthBook flags a sample-ratio mismatch or a suspicious metric anomaly, hinting the variation is broken for some users.

    Pull a sample of affected-variation exposures from the warehouse and copy `subtext_url`.

    ```text theme={null}
    GrowthBook flagged a health issue (SRM / anomaly) on <experiment>. Here are sessions from the
    affected variation:
    <PASTE subtext_urls>

    Look for something breaking that would skew exposure or metrics — JS errors, a variation failing
    to render, users bouncing instantly. Tell me whether the experiment data is trustworthy or
    contaminated, with evidence.
    ```
  </Accordion>
</AccordionGroup>

<Tip>
  A "variation is broken" verdict invalidates the result — re-run after fixing — while a "design underperformed" verdict is a real finding. For a regression across several users, ask your agent to assemble one proof document (`doc-create`) with a screenshot per exposed user, then paste that single link into the GrowthBook experiment notes so the lift number carries its cause.
</Tip>

## For agents

An autonomous agent can run the whole loop against GrowthBook directly: discover the regressed variation, extract the `subtext_url`, and hand each session to the Subtext MCP. GrowthBook ships an official MCP server — `@growthbook/mcp`, open source at `github.com/growthbook/growthbook-mcp` — but it runs locally over stdio (`npx -y @growthbook/mcp@latest`); there is no hosted or remote endpoint. Authenticate with env vars (`GB_API_KEY` is an API key or PAT; `GB_EMAIL` is required for flag and experiment tools). Docs live at `docs.growthbook.io/integrations/mcp`.

<Steps>
  <Step title="Discover the regressed variation">
    Call `get_experiments` with response mode `full` (metadata + results) or `summary` (results + metrics) to surface the regressed variation and metric directly. Without the MCP, hit the REST API — base `https://api.growthbook.io/api` (Cloud) or `https://{domain}/api` (self-host), auth `Authorization: Bearer <API_KEY>`:

    ```
    GET /v1/experiments/{id}/results          # getExperimentResults → aggregated per-variation/metric stats
    ```

    Confirm field names against the OpenAPI spec at `https://api.growthbook.io/api/v1/openapi.yaml`.
  </Step>

  <Step title="Extract the session URL">
    The REST API and `get_experiments` return **aggregated results only** — no raw per-user exposure rows. GrowthBook is warehouse-native, so `subtext_url` lives on the `experiment_viewed` exposure table the `trackingCallback` writes:

    ```sql theme={null}
    SELECT user_id, variation_id, subtext_url
    FROM experiment_viewed
    WHERE experiment_id = '<id>' AND variation_id = '<regressed variant>'
      AND subtext_url IS NOT NULL
    ORDER BY timestamp DESC LIMIT 100;
    ```

    With your own warehouse, the shape of `experiment_viewed` is whatever your `trackingCallback` pipeline writes — no GrowthBook setting changes it, so emit `subtext_url` as its own event property (as above) rather than nesting it. (If you instead use GrowthBook Cloud's Managed Warehouse — where events are ingested via GrowthBook's SDK tracking plugin, not your own `analytics.track` — you can register `subtext_url` as a **Key Attribute** on the data-source settings page to lift it into a top-level column; this applies to new events only.) Note that the MCP's `get_attributes` returns the attribute *schema*, not a user's stored values — it is **not** the path to a per-user `subtext_url`.
  </Step>

  <Step title="Hand off to Subtext">
    For each URL, call `review-open(session_url=<subtext_url>)` on the Subtext MCP, then step through the key moment in the variation for a Detect-vs-Diagnose read: the variation regressed versus did it actually break, or just underperform.
  </Step>
</Steps>

<Note>
  `subtext_url` only round-trips if the `trackingCallback` wrote it into the exposure stream at capture time (see [Attach the session URL](#attach-the-session-url)). No REST endpoint exposes raw exposures or per-user attribute values — per-user reads are warehouse-only. If you forward exposures to an event tracker (Segment / Amplitude / Mixpanel), you can read `subtext_url` off the `experiment_viewed` event there instead.
</Note>

## Related

* [Session Review overview](/docs/session-review/overview) — what your agent does once a session is open.
* [Install the capture snippet](/docs/install/overview) — required before any session URL exists to attach.
