From 68ade36fe6faee3c799e562ba2f84081d8a36439 Mon Sep 17 00:00:00 2001 From: Matt Miermans Date: Thu, 30 Jul 2026 13:41:11 -0400 Subject: [PATCH 01/10] feat(newtab): Add hnt-backend-investigation skill --- plugins/newtab/README.md | 8 + .../skills/hnt-backend-investigation/SKILL.md | 312 ++++++++++++++++++ .../references/data-sources.md | 262 +++++++++++++++ .../references/failure-modes.md | 146 ++++++++ 4 files changed, 728 insertions(+) create mode 100644 plugins/newtab/skills/hnt-backend-investigation/SKILL.md create mode 100644 plugins/newtab/skills/hnt-backend-investigation/references/data-sources.md create mode 100644 plugins/newtab/skills/hnt-backend-investigation/references/failure-modes.md diff --git a/plugins/newtab/README.md b/plugins/newtab/README.md index 407457f..e13bd86 100644 --- a/plugins/newtab/README.md +++ b/plugins/newtab/README.md @@ -9,3 +9,11 @@ Skills and tools for Firefox newtab development. Adds `@nova-cleanup` comments to Firefox newtab code for Project Nova parallel implementation tracking. Use this skill when adding cleanup comments to Nova-related changes. See [`skills/nova-cleanup-comments/SKILL.md`](skills/nova-cleanup-comments/SKILL.md) for details. + +### `hnt-backend-investigation` + +Diagnoses Home New Tab **backend** errors, outages, and data-quality defects — Merino, the curated corpus, admin-api, the article crawler, and the ML section pipeline. + +Use this skill when a Sentry alert fires, an editor reports something broken, or recommendations look empty, wrong, or stale. It confirms the symptom independently, probes competing hypotheses in parallel, stratifies metrics, tries to break its own conclusion, and writes an evidence-backed `FINDINGS.md` naming the check that would have caught the problem earlier. See [`skills/hnt-backend-investigation/SKILL.md`](skills/hnt-backend-investigation/SKILL.md) for details. + +**Needs:** `gh`; `gcloud`/`bq` with a billing project; the `aws` CLI with a read-only SSO profile; a MySQL client plus Mozilla VPN; `uv` or `python3`; the Sentry MCP server if you have it. The skill runs on whatever subset you have — anything unreachable is raised as a task and reported under "Could not measure". diff --git a/plugins/newtab/skills/hnt-backend-investigation/SKILL.md b/plugins/newtab/skills/hnt-backend-investigation/SKILL.md new file mode 100644 index 0000000..a4437b7 --- /dev/null +++ b/plugins/newtab/skills/hnt-backend-investigation/SKILL.md @@ -0,0 +1,312 @@ +--- +name: hnt-backend-investigation +description: Investigates Home New Tab backend errors, outages, and data-quality problems across Merino, the curated corpus, admin-api, the article crawler, and the ML section pipeline. Confirms the symptom independently, probes competing hypotheses in parallel, stratifies metrics, tries to break its own conclusion, then writes an evidence-backed FINDINGS doc naming the check that would have caught it. Use when a Sentry alert fires, an editor reports something broken, or recommendations look empty, wrong, or stale. +--- + +# New Tab (HNT) Backend Investigation + +Diagnose a problem in the services behind Firefox Home New Tab recommendations — Merino, the +curated corpus, admin-api, the article crawler, the ML section pipeline, the data pipelines. Not +for in-tree `browser/extensions/newtab` frontend bugs. + +Your product is a **diagnosis**: a root cause, the evidence for it, the impact quantified, and the +check that would have caught it earlier. Two populations can be affected, and every finding should +say which: **Firefox New Tab clients** (everyone who sees recommendations) and **editors/curators** +(the internal editorial team working through curation-admin-tools and admin-api). + +**How to reach each system, and the traps that will silently give you wrong answers** — read before +your first probe in step 1: [references/data-sources.md](references/data-sources.md) + +**How these systems fail, the invariants worth asserting, and falsification moves that work** — read +before you enumerate hypotheses in step 3, because it will change which ones you take seriously: +[references/failure-modes.md](references/failure-modes.md) + +## How to think about this + +Four habits do most of the work. They matter more than any specific query below. + +**Confirm the symptom before explaining it.** Independently reproduce or measure the reported +problem first, whatever its source. An alert can be miscalibrated, mis-scoped, or watching the +wrong layer, and a description passed through two people can drift. Until you have seen the +symptom yourself in data you pulled, you do not know what you are diagnosing. + +**Form the hypothesis before you run the check.** Write down what you expect to see, and what you +would expect to see if the hypothesis were false. A query designed to confirm and a query designed +to discriminate look similar and are not. The characteristic failure in this domain is a correct +query paired with a wrong inference, stated confidently. + +**Work several lines at once.** When more than one explanation is plausible, probe them in parallel +rather than following the most attractive one to its end. Breadth is cheap in the unmetered planes — +Sentry, repo reads, CloudWatch alarm history, one live Merino request — and tunnel vision is the +expensive failure. It is not free in BigQuery or Zyte: dry-run and select narrow columns before you +fan out. Keep a list of every line you considered and record which you dropped and why. + +**Prefer measuring to reasoning.** Reproduce the request. Run the model on real inputs. Count the +actual rows. Read the code path instead of assuming its behaviour. A plausible mechanism becomes a +number, and only numbers survive review. + +Write like the evidence is asymmetric, because it usually is. Avoid "always", "never", "in every +case"; say what each count is a count of, and whether it is a floor, a ceiling, or a sample. + +## Don't stall on the developer + +Every pause costs wall-clock, because the developer may not be watching. Default to proceeding: +pick the sensible option, say which one you picked, and keep going. Ask a blocking question only +when proceeding on any assumption would waste the whole investigation. + +When a source you cannot reach looks promising, raise it **once** as a pending task carrying the +whole errand in its own text, prefixed `DEV:` so it reads as theirs rather than yours — e.g. +`DEV: connect to VPN so corpus MySQL is reachable — settles whether the DE items exist at all`. +Give the exact command, not a description of the problem; for an interactive login use the `!` +in-session form so the output lands here, e.g. +`! aws --profile sso login`. If the answer lives in a dashboard +or console you have no credential for, do not ask for access at all — name the view, the filter, and +the single number or shape you need, and ask them to read it back. + +**Then keep investigating the other hypotheses immediately. Do not wait.** + +Restate it exactly once more — whichever comes first: they confirm, the unblocked lines run out, or +you are three probes into a line you had already judged weaker than the blocked one. That is the +second and last ask. If there is still no response, finish steps 7 and 8 with `Status: blocked`, +name the one unblock under "Could not measure", and leave the access task pending. + +Stopping outright is a last resort: only when the blocked source is the only thing that can settle +the question **and** it cannot be reduced to a question they can answer for you — a dashboard, +console, or explore is almost never a real stop. A request that quietly disappears is worse than one +that was never made. + +## Workspace + +One directory holds the whole investigation: `~/investigation/-
-`, e.g. +`~/investigation/2026-jul29-empty-de-sections`. Reuse a directory only when its FINDINGS.md is about +the same symptom. Say which directory you chose; do not ask. + +- `FINDINGS.md` — one living document. Create it from the step-7 skeleton **before your first + probe**; steps 2, 4 and 6 write into it as they go, so step 7 is a final pass over a document that + already exists. Rewrite it in place, not as `FINDINGS-v2.md`. +- `queries/` and `results/` — every query as a file, its output alongside under the same basename. +- `api_responses/` — raw JSON from live calls. Save them even when they look boring; metered APIs + cost money to re-hit and the data may be gone tomorrow. +- Aggregate before saving. Counts, rates, and shapes answer nearly every question these + investigations ask; this directory sits outside any repo and gets linked into tickets, so prefer a + distribution over a dump of rows carrying editor identities or user data. +- Rewrite only the FINDINGS.md you wrote and delete only files this session created — anything else + in the directory belongs to another investigation. +- Python work goes in an isolated venv inside this directory: `uv venv` if `uv` is installed, + otherwise `python3 -m venv`. + +## Step 1 — pin down the report + +Establish these five facts. Derive what you can — the alert or issue carries the symptom and its +volume, stratifying the data gives you the surface and locale, a live request tells you whether it +is still happening. Ask only for what is genuinely undiscoverable, in one batch, and keep working +while you wait. + +| Fact | Why it matters | +|---|---| +| Exact symptom, verbatim | "Empty section" and "wrong items in section" have disjoint causes | +| Who noticed, and how | Alert / editor report / spotted by hand — sets what evidence exists | +| First and last seen, **with timezone** | Anchors the timeline; reports usually arrive late | +| Surface, locale, section, client version | The stratum is very often the diagnosis | +| Still happening right now? | Live reproduction vs. historical forensics | + +Then confirm it yourself. + +**If it is still happening, capture the perishable evidence first** — a live +`curated-recommendations` request, current logs, current per-section counts — and save the raw +responses under `api_responses/`. Build the step-2 prior while those probes are in flight; history +keeps, live signal does not. + +**An error-rate signal is not yet a symptom.** Measure the erroring stage's *output* to fix the +blast radius — items per section and freshest item timestamp per surface for assembly, a live Merino +request for anything client-facing — so you can say whether Firefox clients are affected at all. An +error floor that never reaches the output is a not-incident; errors flat with output at zero is +worse than the alert says. When the report is a Sentry alert, resolve it to a concrete error and a +volume, and decompose the issue by error message before trusting its title or its trend — the +alert-quality checks are in `references/failure-modes.md`. + +**If you cannot reproduce it, that is a result, not a blocker.** Record the exact attempt — surface, +locale, time, request, what you saw instead — then switch the question to why the reporter saw it +and you do not: a stratum you did not hit, a window that has closed, a cache, a client version, or a +monitor measuring something other than what it claims. Classify as `not-incident` only after that +second question has an answer; unconfirmed is not refuted. + +A report relayed from an editor is ambiguous between what editors see in curation-admin-tools and +what clients see on the surface. Probe both in parallel and state which population you confirmed. + +The moment you confirm client-visible impact that is still happening, state it in one line — what, +how big, since when — and keep investigating. + +## Step 2 — establish what "normal" is + +You do not know what is currently normal in this system, so you have no basis on which to reject +your own inference. Build that prior yourself before asking for it — most of it is in reach: + +- **Which surfaces are serving** — surfaces present and not disabled in the section data, or a live + Merino request for the locale in question. +- **Whether the crawl is feeding them** — per-surface, per-source volume over the last day from the + crawl data. These two sets differ, and a crawled surface with no sections is not automatically a + bug. +- **What shipped recently** — recent commits in the service repos, releases in Sentry, timestamps on + model or config artifacts. +- **Whether this is normally noisy or seasonal** — a trailing profile of the same metric, by day of + week. Do not ask; compute it. +- **Whether it has happened before** — search Sentry for the same signature, and the service repos' + GitHub issues. + +What is left is genuinely in the developer's head, and it is worth one short, non-blocking ask: +anything in flight that would not show up in a repo or a dashboard, and whether a postmortem or +incident register exists for this area. An existing write-up can answer in a paragraph what costs +hours to reconstruct, so ask early — then carry on without waiting. + +Record what you derive and what they tell you in FINDINGS.md, keeping the two apart. When a later +query contradicts the prior, treat your own inference as the suspect first, and say so. + +## Step 3 — enumerate hypotheses, then probe them in parallel + +List every plausible explanation before testing any of them. Widen the list from +`references/failure-modes.md` — its failure-class tells cover classes the symptom table below does +not, and "Why these failures are silent" names the mechanisms that make a broken pipeline return a +valid-looking empty result rather than an error. + +| Symptom | Likely layer | Probe | +|---|---|---| +| Recommendations 500 / timeout / stale | Merino, or its upstream corpus API | Sentry + live `curated-recommendations` request | +| Section empty or short | ML section assembly → corpus → Merino | Active items per section; freshest item timestamp per section | +| Section missing entirely | Section config / section manager | Section rows for the surface; section-manager errors | +| Wrong / stale / duplicate items | Corpus content or ranking | Trace one item end to end by `corpusItemId` | +| Bad title, image, author, date | Crawl + hydration | Compare the stored item against a live re-extraction | +| Whole publisher or domain absent | Crawl discovery / domain block | Per-domain counts over time, then the vendor's own status data | +| Errors up, no reported user impact | The erroring stage itself | Assert that stage's output invariant downstream, then decompose the errors | +| Editor-facing error or lockout | admin-api → corpus API | Sentry for the editor-facing services; reproduce the mutation | +| Metrics or dashboard wrong | ETL / derived tables | Compare the derived table against its source | + +Before firing, write beside each hypothesis the result that would kill it. Then issue the probes as +independent tool calls in a single batch, backgrounding anything slow and handing a line that needs +several dependent steps to a subagent so the batch still returns together. Cap the first wave at +four or five cheap, independent probes, and hold metered calls and large scans for the second wave. +Name each `queries/` and `results/` pair after its hypothesis, so a result cannot be attributed to +the wrong line. + +Two moves consistently break cases open: **leave the tool you started in** — when a source stops +yielding, measure the same thing somewhere else, since the failure is often invisible in the plane +you began with — and **compare against sibling strata**, which localises a fault faster than reading +code. Both are expanded in `references/failure-modes.md`. + +## Step 4 — build the timeline + +An explicit `timestamp (UTC) | observation | source` table. + +- Normalise every timestamp to UTC and label it. Timezone mismatch is a common source of phantom + gaps, and a subtraction error can manufacture an outage that never happened. +- Extend the window to weeks, not hours — these failures are frequently much older than the report. + Widen freely on the cheap planes; on a billed plane, widen only after a narrow window has shown + you something. +- Distinguish *first occurrence* from *first noticed*, and state both. +- Line the window up against the deploys and config changes from step 2. +- Treat retention limits as limits: a "first seen" date can be the edge of a retention window rather + than onset. See `references/data-sources.md`. + +## Step 5 — stratify before concluding anything + +An aggregate that looks fine is the normal way these problems hide. Slice every metric by +**domain, locale, region, surface, section, experiment branch, and client/addon version**, and look +for a single stratum at zero or down sharply against its own trailing median rather than against +yesterday. When the report says "some users" with no stratum attached, treat experiment branch, +region, and rollout state as the first cuts — some surfaces are reachable only through experiment +enrolment, so an enrolled/unenrolled split is invisible to a locale slice. + +Strata are not interchangeable, and the volumes are strongly seasonal; both traps, and the +per-surface source mix, are detailed in `references/data-sources.md`. Compare each stratum against +its own history. + +**Liveness is not health.** "The job ran successfully" is compatible with total data loss in every +one of these pipelines. Count what came out, compare it against what went in, and never accept a +green run as evidence. + +If the measurements exonerate the backend, stop at the service boundary. Read far enough to name the +owning team and the contract that is being broken, then hand it over rather than continuing into a +system this skill does not cover. A second, unrelated anomaly you trip over on the way is a one-line +note plus a task, not a second investigation. + +## Step 6 — try to break your own conclusion + +Before writing anything down as fact, attack it: + +- Check the boring explanations first: timezone; a filter in your own query; a `LIMIT` silently + truncating; an unrepresentative code path; sampling; a column that does not mean what its name + says; a partially-launched feature. +- Then reach for the falsification moves in `references/failure-modes.md` — the code path, exact + enumeration, repetition, the real limit, the config's history — picking the ones that would break + your specific mechanism. +- **Measure the effect, not just the mechanism.** A guard that provably runs is not evidence that + the outcome is correct; verify the outcome separately. +- Cross-check against a second, independent source. One source is a hypothesis. +- Re-read the step-2 prior. If your conclusion implies that something the prior says is working is + broken, re-check your own measurement first — a filter in your query, a stratum mismatch, or a + wrong surface identifier is the likelier explanation. If the measurement survives that re-check, + the measurement wins: state the contradiction explicitly in FINDINGS.md, note it to the developer + in one line, and keep going. + +Mark every claim **verified**, **inferred**, or **refuted**. Keep the refuted ones in the document; +they stop the next person re-running them. + +## Step 7 — write FINDINGS.md + +```markdown +# — investigation + +**Status:** investigating | root cause identified | blocked +**Class:** outage | degradation | data-quality defect | user-facing bug | near-miss | not-incident +**Severity:** critical | high | medium | low +**Affected:** Firefox clients | editors | both — with a magnitude, not an adjective + +## Summary +Three sentences: what broke, since when, what the user-visible effect is. + +## Timeline (UTC) +| Time | Observation | Source | + +## Prior +**Derived** — per-surface volumes, recent deploys and model artifacts, trailing profile, prior +occurrences. Each with the query or link. +**Reported** — verbatim answers from the developer, or `asked
-`, e.g. -`~/investigation/2026-jul29-empty-de-sections`. Reuse a directory only when its FINDINGS.md is about -the same symptom. Say which directory you chose; do not ask. +Each investigation gets its own subdirectory inside one root directory that the developer keeps their +investigations in. Do not assume where that root is. Establish it in this order: + +1. **Look in user memory.** `~/.claude/CLAUDE.md` is where the root is recorded, because it is the + only memory scope that applies across every repository you might be launched from. If it names a + root, use it and do not ask. +2. **Otherwise ask, once, before your first write.** Where the developer keeps investigations cannot + be derived, and guessing puts files somewhere they did not choose. Ask it as a single question at + the start, and run the step-1 probes while you wait rather than idling. +3. **Then record it.** Append one line to `~/.claude/CLAUDE.md`, creating that file if it does not + exist: `Investigations live in (one subdirectory per investigation).` Tell the developer + you saved it, so they know they will not be asked again. Auto memory is the wrong home for this: + it is keyed to the repository you were launched from, and investigations get started from + whichever service repo is to hand. + +Then create this investigation's subdirectory inside that root. If the root already holds +investigations, follow the naming they use; otherwise `
-`, e.g. +`jul29-empty-de-sections`. Say which directory you created. Reuse an existing one only when its +FINDINGS.md is about the same symptom. - `FINDINGS.md` — one living document. Create it from the step-7 skeleton **before your first probe**; steps 2, 4 and 6 write into it as they go, so step 7 is a final pass over a document that From 346a75af3493867a0744302bf56a52ba13d5240e Mon Sep 17 00:00:00 2001 From: Matt Miermans Date: Thu, 30 Jul 2026 14:59:10 -0400 Subject: [PATCH 10/10] newtab/hnt-backend-investigation: add Slack MCP and channel posting rule --- plugins/newtab/README.md | 1 + .../skills/hnt-backend-investigation/SKILL.md | 22 +++++++++++++++---- .../references/data-sources.md | 22 ++++++++++++++++--- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/plugins/newtab/README.md b/plugins/newtab/README.md index 9674925..5feec90 100644 --- a/plugins/newtab/README.md +++ b/plugins/newtab/README.md @@ -24,5 +24,6 @@ Use this skill when a Sentry alert fires, an editor reports something broken, or - a MySQL client, plus Mozilla VPN - `uv` or `python3` - the Sentry MCP server +- the Slack MCP server, for `#hnt-dev-be-alerts` Anything unreachable is raised as a task and reported under "Could not measure". diff --git a/plugins/newtab/skills/hnt-backend-investigation/SKILL.md b/plugins/newtab/skills/hnt-backend-investigation/SKILL.md index 623506c..ebd5ed0 100644 --- a/plugins/newtab/skills/hnt-backend-investigation/SKILL.md +++ b/plugins/newtab/skills/hnt-backend-investigation/SKILL.md @@ -54,8 +54,9 @@ pick the sensible option, say which one you picked, and keep going. Ask a blocki when proceeding on any assumption would waste the whole investigation. When a source you cannot reach looks promising, raise it **once** as a pending task carrying the -whole errand in its own text, prefixed `DEV:` so it reads as theirs rather than yours — e.g. -`DEV: connect to VPN so corpus MySQL is reachable — settles whether the DE items exist at all`. +whole errand in its own text, prefixed `User action:` so it reads as theirs rather than yours — e.g. +`User action: connect to VPN so corpus MySQL is reachable — settles whether the DE items exist at +all`. Give the exact command, not a description of the problem; for an interactive login use the `!` in-session form so the output lands here, e.g. `! aws --profile sso login`. If the answer lives in a dashboard @@ -156,7 +157,8 @@ A report relayed from an editor is ambiguous between what editors see in curatio what clients see on the surface. Probe both in parallel and state which population you confirmed. The moment you confirm client-visible impact that is still happening, state it in one line — what, -how big, since when — and keep investigating. +how big, since when — and keep investigating. If that deserves a heads-up in `#hnt-dev-be-alerts`, +follow the posting rule at the end of step 8. ## Step 2 — establish what "normal" is @@ -172,7 +174,8 @@ your own inference. Build that prior yourself before asking for it — most of i model or config artifacts. - **Whether this is normally noisy or seasonal** — a trailing profile of the same metric, by day of week. Do not ask; compute it. -- **Whether it has happened before** — search Sentry for the same signature, and the service repos' +- **Whether it has happened before** — search Sentry for the same signature, recent alerts in + `#hnt-dev-be-alerts`, and the service repos' GitHub issues. What is left is genuinely in the developer's head: anything in flight that a repo or a dashboard @@ -319,3 +322,14 @@ Any telemetry plane you could not reach, and the access that would unblock it. - **Leave the follow-up as a task**, owned by whoever will act: confirm the fix shipped and the metric actually recovered. State that you have done so rather than asking whether to. - Hand back a two-line verdict plus the FINDINGS.md path. Do not paste the document into chat. + +### Posting to `#hnt-dev-be-alerts` + +Updates worth sharing go to `#hnt-dev-be-alerts`. Search the channel first: if the investigation +started from an alert or message posted there in the last day or two, reply **in that thread**, so the +diagnosis stays attached to the alert people already saw. Only start a new thread when nothing recent +relates. + +**Ask before every single message, showing the exact text and where it will go.** Approval for one +post is not approval for the next, and this is a shared channel colleagues act on. If approval does +not come, keep the draft in FINDINGS.md and carry on; an unposted update is not a reason to stop. diff --git a/plugins/newtab/skills/hnt-backend-investigation/references/data-sources.md b/plugins/newtab/skills/hnt-backend-investigation/references/data-sources.md index c28afe2..1ced8f1 100644 --- a/plugins/newtab/skills/hnt-backend-investigation/references/data-sources.md +++ b/plugins/newtab/skills/hnt-backend-investigation/references/data-sources.md @@ -7,6 +7,7 @@ Reference for the `hnt-backend-investigation` skill. - The pipeline, and which repo owns each stage - Vocabulary: surface, locale, section, and the id to trace items by - Sentry — projects and the traps in reading them +- Slack — `#hnt-dev-be-alerts`, as alert history and as where updates go - Merino — live requests and GCP projects - BigQuery — the tables worth knowing and the traps in reading them - Curated corpus MySQL — access and schema shape @@ -73,10 +74,10 @@ alone will not decompose an umbrella fingerprint. **If no `mcp__sentry__` tools are available, the server is not set up in this session, and you should say so immediately.** Do not weigh this one against your other lines first; most symptoms here are error-shaped, and steps 1, 2 and 6 all read from Sentry, so an investigation without it is working -half-blind. Raise it as a `DEV:` task with the command: +half-blind. Raise it as a `User action:` task with the command: ``` -! claude mcp add --transport http sentry https://mcp.sentry.dev/mcp +! claude mcp add --scope user --transport http sentry https://mcp.sentry.dev/mcp ``` followed by `/mcp` to authenticate in the browser. Servers load when a session starts, so the tools @@ -106,6 +107,20 @@ Traps: - Absence of events is weak evidence: a process that dies at startup, or a job that drops items while reporting success, emits nothing. +## Slack + +`#hnt-dev-be-alerts` carries the backend alerts for this stack, which makes it two things at once: a +record of what has already fired, worth searching in step 2 before you conclude something is new, and +the place investigation updates go. Reachable through the Slack MCP server, whose tools are prefixed +`mcp__slack__`: + +``` +! claude mcp add --scope user --transport http slack https://mcp.slack.com/mcp +``` + +Reading it is free. Posting needs the developer's approval for **each** message, and prefers a reply +in the existing alert thread over a new one: the rule is at the end of step 8 in SKILL.md. + ## Merino Reproducing the client call is often the fastest confirmation of a client-visible symptom: @@ -273,8 +288,9 @@ Each is a short errand: give the developer the command, not a description of the | No billing project configured | Name one they can bill, usually `moz-fx-dev--sandbox`, or set it with `gcloud config set project ` | | Permission denied on a dataset or a Merino project | Request read access, or viewer on the project; say meanwhile whether the question is about payload shape, which stage can answer | | Zyte key missing from the environment | Create one at https://app.zyte.com/o/612928/zyte-api/api-access, `export ZYTE_API_KEY=` in the shell they launch from, and restart the session. Or have them run the single extraction and paste back the JSON, not the key | -| No `mcp__sentry__` tools at all | Run `! claude mcp add --transport http sentry https://mcp.sentry.dev/mcp`, then `/mcp` to authenticate; the tools appear after a session restart | +| No `mcp__sentry__` tools at all | Run `! claude mcp add --scope user --transport http sentry https://mcp.sentry.dev/mcp`, then `/mcp` to authenticate; the tools appear after a session restart | | Sentry connected but unauthenticated or scoped too narrowly | Run `/mcp` and authenticate for the `mozilla` org, or read back the issue's event counts broken down by error message | +| No `mcp__slack__` tools, and an update needs posting | Run `! claude mcp add --scope user --transport http slack https://mcp.slack.com/mcp`, then `/mcp` to authenticate; or post the drafted message to `#hnt-dev-be-alerts` themselves | | Editor-facing symptom needs an authenticated session | Reproduce the click themselves and report the exact error text and time | | The answer is in a dashboard you cannot reach | Open it, apply the specific filter you name, and read back the one number or shape you asked for |