# Recce Review

> Review dbt model data changes using Recce. Triggers when: user asks to review data changes, check data impact, run recce review, validate model changes before committing, review a Recce Cloud PR session, connect MCP to a cloud session, pastes a GitHub PR / GitLab MR URL, or pastes a Recce Cloud session/launch URL for cloud-mode review.

- Skill: `datarecce/recce-review` (Agent Skill, multi-file: 10 files)
- Install (CLI): `npx skillmds@latest add datarecce/recce-review`
- Raw SKILL.md: https://api.skillmd.com/api/skills/datarecce/recce-review/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: datarecce (https://skillmd.com/u/datarecce)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/datarecce/recce-review

---


# /recce-review — Data Review Orchestration

This skill orchestrates tracked-model handoff, sub-agent dispatch, post-review cleanup, and risk-based next-step suggestions.

It also handles **cloud-mode flips** from any of:

- a **PR URL** (GitHub) or **MR URL** (GitLab, incl. self-hosted) — the skill fetches PR/MR comments and extracts the Recce Cloud session ID
- a **Recce Cloud session URL** (`.../sessions/<UUID>`) or **launch URL** (`.../launch/<UUID>`) — the skill extracts the session ID directly, no SCM access required (useful for any Cloud host: production, staging, localhost dev)
- a **bare session ID** (UUID)

In all cases the skill verifies cloud authentication and flips the running MCP server into cloud mode by calling its `set_backend` tool — no reconnect, no restart.

Claude Code launches `recce mcp-server` (stdio) at session start in **local mode** and the same server stays alive for the whole session. Mode switching happens **inside** that running server via MCP tool calls.

Follow these steps in order.

---

## Step 0: Cloud-mode resolution (only if user provided a relevant URL or asked for cloud review)

> Skip this step if the user did not provide a PR/MR URL, a Cloud session/launch URL, a bare UUID, and did not mention "cloud", "cloud session", or "Recce Cloud".

### 0.1 Classify the input and resolve the session ID

Examine the user's input and pick the matching path. The session-ID UUID format used below is `[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`.

**Path A — Recce Cloud URL (fast path, no SCM access needed):**

If the input matches either of these path shapes (any host — `cloud.reccehq.com`, `staging.cloud.reccehq.com`, `localhost:3000`, etc.):

- `<scheme>://<host>/launch/<UUID>`
- `<scheme>://<host>/sessions/<UUID>`

…extract the UUID, set `SESSION_ID` to it, and **skip directly to Step 0.4**. The session ID under `/launch/` and `/sessions/` is the same identifier consumed by `set_backend(session_id=...)`.

**Path B — Bare UUID:**

If the input is just a UUID matching the regex above, set `SESSION_ID` to it and **skip directly to Step 0.4**.

**Path C — PR/MR URL:**

If the input is a GitHub PR URL (`https://github.com/<owner>/<repo>/pull/<n>`), a GitHub PR number (when the working directory is already a GitHub repo), or a GitLab MR URL (`https://<host>/<group>[/<subgroup>...]/<project>/-/merge_requests/<iid>`, works for `gitlab.com` and self-hosted hosts), continue to Step 0.2.

**Path D — Nothing matched:**

Ask: "Which session should I review? Paste a Recce Cloud session URL (e.g., `https://cloud.reccehq.com/launch/<UUID>`), a GitHub PR URL, a GitLab MR URL, or a session UUID."

### 0.2 Detect the SCM and verify access

> Steps 0.2 and 0.3 run **only for Path C** (PR/MR URL). If you arrived here from Path A or Path B in 0.1 with a `SESSION_ID` already in hand, skip to Step 0.4.

First identify which source-control host owns the URL:

```bash
bash ${CLAUDE_PLUGIN_ROOT}/skills/recce-review/scripts/scm/detect.sh "<PR_OR_MR_URL>"
```

The script prints exactly one of `SCM=github`, `SCM=gitlab`, `SCM=bitbucket`, or `SCM=unknown`. Detection is path-based (recognizes `/pull/`, `/-/merge_requests/`, `/pull-requests/`) so it works for self-hosted hosts.

Then run the matching readiness check:

**If `SCM=github`:**

```bash
bash ${CLAUDE_PLUGIN_ROOT}/skills/recce-review/scripts/scm/github-ready.sh
```

The script prints `GITHUB=ready` (with `GITHUB_VIA=cli`) or `GITHUB=unavailable`. If unavailable, tell the user: "GitHub CLI is not authenticated. Run `gh auth login` and re-run /recce-review." Stop.

**If `SCM=gitlab`:**

```bash
bash ${CLAUDE_PLUGIN_ROOT}/skills/recce-review/scripts/scm/gitlab-ready.sh
```

GitLab access works via either the `glab` CLI **or** a `GITLAB_TOKEN` environment variable (either is sufficient; both work for self-hosted GitLab). The script prints `GITLAB=ready` (with `GITLAB_VIA=cli` when `glab auth status` succeeds, otherwise `GITLAB_VIA=token`) or `GITLAB=unavailable`.

If unavailable, tell the user: "No GitLab credentials found. Either run `glab auth login` (for self-hosted, use `glab auth login --hostname <host>`) or export `GITLAB_TOKEN=<your-personal-access-token>` (with `read_api` scope), then re-run /recce-review."

Stop on unavailable.

**If `SCM=bitbucket` or `SCM=unknown`:** tell the user the URL's source-control host is not yet supported by this skill (Bitbucket support is planned). Stop.

### 0.3 Fetch PR/MR comments and parse the session ID

Run the comments fetcher matching the detected SCM:

**If `SCM=github`:**

```bash
bash ${CLAUDE_PLUGIN_ROOT}/skills/recce-review/scripts/scm/github-comments.sh "<PR_REF>"
```

`<PR_REF>` is the PR URL or PR number.

**If `SCM=gitlab`:**

```bash
bash ${CLAUDE_PLUGIN_ROOT}/skills/recce-review/scripts/scm/gitlab-comments.sh "<MR_URL>"
```

`<MR_URL>` must be the full URL (the script parses host, project path, and IID from it). The script prefers `glab api` when available (which already knows about self-hosted host config), falling back to `curl` against `https://<host>/api/v4` with `GITLAB_TOKEN`.

Both scripts print one comment/note body per record on stdout. Search those bodies for a Recce Cloud session URL of the form `<scheme>://<host>/(sessions|launch)/<UUID>`. Production comments use `cloud.reccehq.com`, but other hosts (`staging.cloud.reccehq.com`, `localhost:3000`) and the `/launch/` path variant are also valid — accept any host and either path. `<UUID>` is `[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`.

- Exactly one match — show it and confirm with the user.
- Multiple distinct matches — list each match with the surrounding comment body so the user can identify which session is the relevant one (typically the most recent Recce Cloud bot comment after a force-push). Ask the user to choose. The fetcher scripts return only comment bodies, not author/timestamp metadata — disambiguation is done by visible content.
- No match — tell the user: "No Recce Cloud session URL found in PR/MR comments. Run `recce-cloud list --type pr` to find an existing session, or `recce-cloud upload` from the PR/MR branch to create one — then paste the session ID (UUID) here." Wait for input and validate against the UUID regex.

### 0.4 Verify Recce Cloud authentication

Recce Cloud credentials live in **`~/.recce/profile.yml`** (key: `api_token`) or in the **`RECCE_API_TOKEN`** environment variable. Either is sufficient; `RECCE_API_TOKEN` takes precedence.

Run:

```bash
bash ${CLAUDE_PLUGIN_ROOT}/skills/recce-review/scripts/check-recce-auth.sh
```

The script prints exactly one of `AUTH=env`, `AUTH=file`, or `AUTH=missing`. It does not print or transmit the token itself — it only reports which source supplied it.

- `AUTH=env` or `AUTH=file` — proceed to Step 0.5.
- `AUTH=missing` — offer to run the OAuth flow inline. Ask the user, verbatim:

  > Recce Cloud credentials not found in `~/.recce/profile.yml` and `RECCE_API_TOKEN` is not set.
  >
  > Would you like me to run `recce connect-to-cloud` now? It opens your browser for the OAuth flow and writes `api_token` back into `~/.recce/profile.yml` on success. (Typically ~30s. The command starts a short-lived local HTTP server on a random port to receive the OAuth callback — make sure no firewall blocks loopback callbacks and that the browser it opens is on this machine. On a headless/remote shell, the command will print the URL and wait; you can open that URL on your host machine to complete the flow.)

  Then handle the user's reply:

  **If the user says yes** — run the command via Bash with a generous timeout (the user must click through the OAuth flow in a browser):

  ```bash
  recce connect-to-cloud
  ```

  Pass `timeout: 600000` (10 minutes) to the Bash tool so the command isn't killed mid-flow. Stream the command's stdout to the user — if it prints a URL ("Please visit ..."), the user may need to open it manually.

  After the command exits (regardless of how), re-run the auth probe:

  ```bash
  bash ${CLAUDE_PLUGIN_ROOT}/skills/recce-review/scripts/check-recce-auth.sh
  ```

  - If it now prints `AUTH=env` or `AUTH=file` — proceed to Step 0.5.
  - If it still prints `AUTH=missing` — tell the user: "OAuth flow did not complete (canceled, timed out, or the callback was blocked). You can run `recce connect-to-cloud` manually and re-run `/recce-review` with the same PR/MR." Then **stop**.

  **If the user says no, or declines** — tell the user, verbatim, then **stop**:

  > To authenticate later, run `recce connect-to-cloud` (this opens a browser for the OAuth flow and writes `api_token` back into `~/.recce/profile.yml`), then re-run `/recce-review` with the same PR/MR.

### 0.5 Flip the running MCP server into cloud mode

Call the `set_backend` MCP tool on the `recce` server:

> `mcp__plugin_recce_recce__set_backend(mode="cloud", session_id="<SESSION_ID>")`

Then call `mcp__plugin_recce_recce__get_server_info` and verify the response reports the cloud backend with the matching `session_id`.

`set_backend` returns quickly. The MCP server begins serving cloud-mode requests immediately, but the **Recce Cloud instance behind the session may still be warming up** (cold-start ~30 seconds). During warmup:

- **Metadata tools work right away** — `lineage_diff`, `schema_diff`, `get_model`, `get_cll`, `select_nodes`, `get_server_info` are served from artifacts and do not depend on the warming instance.
- **Data-path tools return HTTP 405** until ready — `row_count_diff`, `profile_diff`, `value_diff`, `value_diff_detail`, `top_k_diff`, `histogram_diff`, `query`, `query_diff`. The 405 body usually says "please try after 10–30 seconds".

The skill therefore does **not** probe upfront — that would only exercise metadata tools (which always succeed) and tell us nothing. Instead, it lets the review proceed immediately and handles 405 reactively when the agent's first data-path tool call lands. See Step 2 for the retry contract.

Outcomes for the flip itself:

- **`set_backend` succeeds and `get_server_info` confirms `mode=cloud`** — tell the user, verbatim, replacing `<SESSION_ID>` with the resolved value:

  ```
  Recce MCP flipped to cloud mode.
    Session: <SESSION_ID>
  Starting the review. The first data-path call may pause briefly while the
  Cloud instance warms up (up to ~30 seconds on a cold session).
  ```

  Then continue inline to **Step 1**.

- **`set_backend` fails with a missing/expired token error** — tell the user:

  > The `recce` MCP server rejected the cloud flip with an authentication error. Run `recce connect-to-cloud` to refresh the token, then re-run `/recce-review` with the same PR/MR. (`recce connect-to-cloud` opens a browser for the OAuth flow and writes `api_token` back into `~/.recce/profile.yml`.)

  Stop.

- **Tool not found (`set_backend` missing)** — tell the user:

  > Your installed `recce` predates cloud-mode MCP. Upgrade with `pip install -U 'recce[mcp]'` and restart Claude Code (`/mcp` reconnect picks up the new binary).

  Stop.

- **`set_backend` fails for another reason** — surface the error message verbatim and stop. Do **not** silently fall back to local mode.

> To return to local mode later: call `mcp__plugin_recce_recce__set_backend(mode="local", project_dir="<absolute-project-path>")`. The skill exposes this as the user-visible action `/recce-review local` (no PR/MR argument and an explicit "local" keyword) — handle it in Step 0 by skipping PR/MR resolution and calling `set_backend(mode="local", ...)` directly.

---

## Step 1: Determine Model Scope

The model scope source depends on the active backend.

### 1A. Cloud mode (Step 0.5 succeeded)

**Skip `get-tracked-models.sh` entirely.** The tracked-changes file captures the *local* user's SQL edits via the PostToolUse hook — it is unrelated to what the cloud session contains. A reviewer who happens to have unrelated local edits would otherwise have those misidentified as the PR/MR's changes.

In cloud mode, set the model scope to the dbt selector `state:modified+`. The MCP server resolves this against the cloud session's stored manifests (base vs. head), so it returns the PR/MR author's actual changes regardless of the reviewer's local working tree. Skip to Step 2.

### 1B. Local mode (Step 0 was skipped, or no cloud flip occurred)

Run:

```bash
bash ${CLAUDE_PLUGIN_ROOT}/skills/recce-review/scripts/get-tracked-models.sh
```

The script reads the project-scoped tracked-changes file written by the PostToolUse hook (`track-changes.sh`) and prints either `TRACKED=false`, or `TRACKED=true` followed by `MODEL_COUNT=<n>` and `MODELS=<comma+space separated names>`.

Parse the output:
- If `TRACKED=true` — record the `MODELS` value (comma-separated model names). Use these in Step 2.
- If `TRACKED=false` — no tracked changes file exists. Do **not** abort. Do **not** ask the user for model names. The agent will use `state:modified+` as a fallback selector.

---

## Step 2: Dispatch Review Agent

Use the `agent:` tool to dispatch `recce-reviewer`. The MCP server is owned by Claude Code (stdio child of `.mcp.json`); if it is not connected, the agent's tool calls will fail and Claude Code will surface the error in `/mcp`. The skill does not start or health-check MCP itself.

**If cloud mode (Step 1A):**
Include in the dispatch context:
> "Active backend is cloud (session `<SESSION_ID>`). Use `state:modified+` as the selector. The MCP server resolves this against the cloud session's stored manifests. Do **not** read the local tracked-changes file — local edits, if any, are unrelated to the PR/MR being reviewed."

**If local mode and tracked models were found (Step 1B returned TRACKED=true):**
Include in the dispatch context:
> "Changed models (from tracked file): {MODELS}. Focus review on these models using selector: {model1}+ {model2}+ (one per model from the list)."

**If local mode and no tracked models (Step 1B returned TRACKED=false):**
Include in the dispatch context:
> "No tracked changes file found. Use state:modified+ as the default selector to review all modified models."

**Context passthrough:** If the user's request includes any of the following, include it in the dispatch message so the reviewer can validate findings against intent:
- **Stakeholder request** (who asked for the change and what they asked for)
- **PR/MR description** (what the change claims to do)
- **Change rationale** (why the change was made)

Format: `Context: [stakeholder] requested '[request]'. PR/MR says: '[description]'.`

This enables the reviewer's context validation step (Step 4 in the agent workflow).

**Cloud warmup retry contract (only when the active backend is cloud — check via `get_server_info` if unsure):**

Include in the dispatch context:

> "Active backend is cloud. Data-path tools (`row_count_diff`, `profile_diff`, `value_diff`, `value_diff_detail`, `top_k_diff`, `histogram_diff`, `query`, `query_diff`) may return HTTP 405 with a 'try again' message while the Cloud instance warms up. On 405, retry the same call up to 5 times with 10-second waits between attempts (~40 seconds total). Metadata tools (`lineage_diff`, `schema_diff`, `get_model`, `get_cll`, `select_nodes`) are not affected — run those first; they will succeed immediately and you can produce partial findings while the instance warms. If a data-path tool is still 405 after the retry budget, report that the Cloud instance did not become ready and recommend the user re-run /recce-review in ~30 seconds."

Wait for the agent to complete and capture its full output.

---

## Step 3: Post-Review Cleanup (on success only)

Check if the agent's output contains `## Data Review Summary`.

**If YES** (successful review):

The cleanup is **mode-gated**, mirroring Step 1:

- **Cloud mode (Step 0.5 succeeded)** — do **not** run `clear-tracked-models.sh`. The local tracked-changes file reflects the reviewer's own un-reviewed local edits, which are unrelated to the cloud session that was just reviewed. Erasing it would silence the pre-commit guard for those un-reviewed local edits.
- **Local mode** — run:

  ```bash
  bash ${CLAUDE_PLUGIN_ROOT}/skills/recce-review/scripts/clear-tracked-models.sh
  ```

  The script removes the project-scoped tracked-changes file (`rm -f`, so it is a no-op if the file is absent) and prints `CLEARED=<path>`. This clears tracked changes so the pre-commit guard no longer warns about already-reviewed models.

**If NO** (agent error or incomplete review):

Do **not** delete the file. Tell the user: "Review did not complete successfully. Tracked changes preserved for retry. Run /recce-review again." Then **STOP** — do not proceed to Step 4.

---

## Step 4: Next Steps Based on Risk Level

> Skip this step if the review did not complete successfully (no `## Data Review Summary` found in Step 3).

Parse the risk level from the agent's summary output (look for `Risk level: HIGH`, `Risk level: MEDIUM`, or `Risk level: LOW`).

- **HIGH**: "Destructive schema changes detected. Consider running `/recce-check` for detailed profile and query analysis before committing."
- **MEDIUM**: "Row count changes detected. Review the deltas above, then commit when satisfied."
- **LOW**: "No significant data impact detected. Looks safe to commit."

