From f3728415f100091707566d7eaee21ad6376f931e Mon Sep 17 00:00:00 2001 From: Evan Purkhiser Date: Wed, 5 Aug 2026 15:08:25 -0400 Subject: [PATCH 1/2] ref(skills): replace sentry-create-alert with sentry-create-monitor The old skill only covered the response half of Sentry's model, so it had no answer for "alert me when latency crosses 800ms" -- that needs a monitor to open the issue before an alert can act on it. It also froze a copy of the workflow-engine payload schema into the skill, which had already drifted: the condition table was missing several filter types and every trigger type real orgs use. The replacement points at the live API reference instead, which Sentry serves as Markdown at docs.sentry.io/api/monitors/.md. The alert condition catalog alone is ~20k characters and changes without notice, so a frozen copy is a liability rather than a convenience. The skill now carries the procedure and the traps; the reference carries the fields. Both are needed because the reference alone is misleading. It documents `metric_issue` as though it were the only monitor type, when the endpoint accepts anything registered with a detector validator -- uptime, cron and mobile-builds monitors are all creatable. It also implies PUT replaces an object when detector updates run partial, and lists `frequency`'s UI presets in a way that reads as an enum when the schema takes any integer. Uptime monitors skip the API entirely: the MCP has create, update and delete tools for them, needing neither a token nor a hand-built payload. Two facts that cost real debugging time are stated up front. An organization auth token authenticates as an anonymous user, and the detector endpoints reject anonymous callers while the workflow endpoints accept them -- so an org token creates alerts fine and 401s on listing monitors, which looks arbitrary rather than like a bad token. And while PUT merges at the top level, any array in the body is authoritative, so sending `actionFilters: []` deletes every filter and action under it. The monitors/alerts concept doc picks up the naming mismatch that makes this API hard to navigate -- monitors are `detectors` in the URL, alerts are `workflows`, and `/monitors/` is the legacy Crons API that shouldn't be used at all. --- README.md | 2 +- scripts/build-skill-tree.sh | 2 +- src/SKILL_TREE.md | 4 +- src/references/concepts/crons.md | 6 + src/references/concepts/monitors.md | 58 ++- src/skills/sentry-create-alert/SKILL.md | 216 ----------- src/skills/sentry-create-monitor/SKILL.md | 337 ++++++++++++++++++ .../sentry-create-monitor/references.yml | 7 + src/skills/sentry-get-started/SKILL.md | 14 +- 9 files changed, 409 insertions(+), 237 deletions(-) delete mode 100644 src/skills/sentry-create-alert/SKILL.md create mode 100644 src/skills/sentry-create-monitor/SKILL.md create mode 100644 src/skills/sentry-create-monitor/references.yml diff --git a/README.md b/README.md index 86a289e6..18148cf7 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ To build any target locally, run `src/plugins//build.sh ` (`c | Skill | What it does | | --- | --- | | `sentry-otel-exporter-setup` | Configure OTel Collector with the Sentry exporter for multi-project routing | -| `sentry-create-alert` | Create alerts via the Sentry workflow engine API | +| `sentry-create-monitor` | Create and edit monitors and the alerts that act on them, against the live API schema | | `sentry-snapshots-cocoa` | Set up Sentry Snapshots for Apple/Cocoa projects | ### Reference Library diff --git a/scripts/build-skill-tree.sh b/scripts/build-skill-tree.sh index a4483e06..e5016af5 100755 --- a/scripts/build-skill-tree.sh +++ b/scripts/build-skill-tree.sh @@ -146,7 +146,7 @@ You are **Sentry's AI assistant**. You help developers set up Sentry, debug prod 1. **Ask first.** Greet the user and ask what they'd like help with. Present these options: - **Set up Sentry** — Add error monitoring, performance tracing, session replay, or AI/LLM monitoring to a project - **Debug a production issue** — Investigate errors and exceptions using Sentry data - - **Configure a feature** — alerts, OpenTelemetry pipelines + - **Configure a feature** — monitors and alerts, OpenTelemetry pipelines - **Review code** — Resolve Sentry bot comments or check for predicted bugs 2. **Wait for their answer.** Do not proceed until the user tells you what they want. diff --git a/src/SKILL_TREE.md b/src/SKILL_TREE.md index 07eda5ef..726ba2e5 100644 --- a/src/SKILL_TREE.md +++ b/src/SKILL_TREE.md @@ -9,7 +9,7 @@ You are **Sentry's AI assistant**. You help developers set up Sentry, debug prod 1. **Ask first.** Greet the user and ask what they'd like help with. Present these options: - **Set up Sentry** — Add error monitoring, performance tracing, session replay, or AI/LLM monitoring to a project - **Debug a production issue** — Investigate errors and exceptions using Sentry data - - **Configure a feature** — alerts, OpenTelemetry pipelines + - **Configure a feature** — monitors and alerts, OpenTelemetry pipelines - **Review code** — Resolve Sentry bot comments or check for predicted bugs 2. **Wait for their answer.** Do not proceed until the user tells you what they want. @@ -26,7 +26,7 @@ Each one is self-contained and named for the job it does. If you're not sure wha | Skill | What it does | |---|---| -| [`sentry-create-alert`](skills/sentry-create-alert/SKILL.md) | Create Sentry alerts using the workflow engine API. Use when asked to create alerts, set up notifications, configure issue priority alerts, or build workflow automations. Supports email, Slack, PagerDuty, Discord, and other notification actions. | +| [`sentry-create-monitor`](skills/sentry-create-monitor/SKILL.md) | Create and edit Sentry monitors and the alerts that act on them — metric monitors with fixed, percentage-change, or anomaly-detection thresholds, uptime monitors, cron monitors, mobile app-size monitors, and alerts that notify Slack, email, PagerDuty, Discord, or open a Jira/GitHub ticket. Use when asked to monitor a metric or an endpoint's uptime, set up an alert or notification, change a threshold, route issues to a channel, or list and disable existing monitors and alerts. | | [`sentry-debug-issue`](skills/sentry-debug-issue/SKILL.md) | Debug and fix a Sentry issue — find it (by link, ID, or search), pull full context (stack trace, breadcrumbs, trace, logs), optionally run Seer root-cause / autofix, apply the code fix, and resolve it via a `Fixes PROJECT-NAME-12A` commit/PR. Use when working a known error or hunting one down to fix. | | [`sentry-fix-stack-traces`](skills/sentry-fix-stack-traces/SKILL.md) | Make Sentry stack traces readable — upload source maps for JavaScript/TypeScript, or debug files for native and mobile (dSYM, ProGuard/R8, NDK symbols, Dart obfuscation maps, .NET PDBs). Use when frames in Sentry show minified names, bundled paths, hex addresses, "unknown", or method names with no file/line, instead of your original source. | | [`sentry-get-started`](skills/sentry-get-started/SKILL.md) | Guided entry point for using Sentry through your agent. Orients you to your current setup and, for a new project, sets up Sentry end to end with sane defaults — provision a project, install the SDK (errors, tracing, and whatever it enables by default), and confirm real telemetry reaches Sentry. Routes other intents (adding more signals, fixing issues) to the right skill. | diff --git a/src/references/concepts/crons.md b/src/references/concepts/crons.md index 2e97d522..4a21618e 100644 --- a/src/references/concepts/crons.md +++ b/src/references/concepts/crons.md @@ -43,6 +43,12 @@ a backup — and “it didn’t run” is as bad as “it crashed.” `ok`/`error`) detects *missed* but not *`max_runtime`* timeouts. - **Use a stable, descriptive slug** (`nightly-invoice-sync`, not `job-1`) — it’s the check-in key, so slug churn on every deploy orphans monitors. +- **Two ways to declare the config.** The SDKs take a `monitor_config` alongside the + check-in and upsert the monitor from it, which keeps the schedule in version control + next to the job it describes — usually the one to prefer while instrumenting. + Otherwise create the monitor through the detectors API ([`monitors.md`](monitors.md)), + never the legacy `/organizations/{org}/monitors/` one. + Either way the monitor stays silent until check-ins arrive. ## Related diff --git a/src/references/concepts/monitors.md b/src/references/concepts/monitors.md index b85e0f01..f98ffc6a 100644 --- a/src/references/concepts/monitors.md +++ b/src/references/concepts/monitors.md @@ -13,11 +13,27 @@ Monitors detect; Alerts respond. They’re configured independently: one alert can watch many monitors/projects, and one monitor can feed several alerts. -> Terminology: this model uses **Metric Monitor** for the detection stage and reserves -> **Alert** for the response stage. -> Older docs and integrations still say “metric **alert**” for the same detection -> concept — treat them as the same thing; the rename isn’t fully settled across the -> product. +## Naming — the product and the API disagree + +The API kept the engine’s original vocabulary while the product settled on this one. +Every mismatch below shows up in something you will read — a URL, a response body, an +API reference page, an older doc — so learn them before touching the API: + +- **A Monitor is a `detector` in the API.** The endpoints are + `/organizations/{org}/detectors/`, and every monitor type is one: `metric_issue`, + `uptime_domain_failure`, `monitor_check_in_failure` (cron), `preprod_size_analysis` + (mobile builds), plus the auto-created `error` and `issue_stream`. The API reference + files these pages under “Monitors & Alerts” while every URL in them says `detectors`. +- **`/organizations/{org}/monitors/` is the legacy Crons API — don’t use it.** It is the + path you would guess from the word “monitor,” it predates this model, and it reaches + cron monitors alone. + Those same cron monitors are detectors, so `/detectors/` manages them along with every + other type. +- **An Alert is a `workflow` in the API** — `/organizations/{org}/workflows/`, named for + the workflow engine that evaluates it. +- **“Metric alert” means Metric Monitor.** Older docs and integrations use the old name + for the detection stage; treat them as the same thing. + The rename isn’t fully settled across the product. ## Monitors — when a signal becomes an issue @@ -61,14 +77,30 @@ An alert is **sources → triggers → filters → actions**: ## Coverage honesty -Alert creation is automatable via Sentry’s workflow-engine API; several monitor types -(uptime, dashboards) are heavier UI/API hand-offs today — be upfront about what the -agent can do end-to-end vs. -where it walks the user through the UI. The MCP is **read-only** here: it can inspect -alert rules (`find_alert_rules`, `get_alert_rule`), cron monitors and their check-ins -(`find_monitors`, `get_monitor_details`), and dashboards — useful for verifying after -creation — but there is no create or update path for any of them, and uptime monitors -have no MCP surface at all. +Alerts and **every custom monitor type** are creatable and editable end-to-end through +Sentry’s workflow-engine API — metric, uptime, cron, and mobile builds monitors are all +detectors there. +Only the metric payload has an API reference page, so building the other +three means mirroring the shape of an existing monitor of that type; the accepted types +also depend on what is enabled for the organization. + +Two things the API alone doesn’t finish. +A **Cron Monitor** is inert until the job sends check-ins, which is instrumentation work +— and because the SDKs upsert a monitor from the `monitor_config` they send with a +check-in, a cron monitor can be defined entirely from code, keeping the schedule in +version control beside the job it describes. +**Uptime** and **Cron** monitors also consume a seat, so on an org with none free they +are created **disabled** rather than rejected. + +**Uptime monitors are the exception to reaching for the API at all** — the MCP creates, +updates and deletes them directly (`create_uptime_monitor` and friends), which needs no +auth token and no hand-built payload. +For everything else the MCP is read-only: it inspects alert rules (`find_alert_rules`, +`get_alert_rule`), cron monitors and their check-ins (`find_monitors`, +`get_monitor_details`), and uptime check results (`get_uptime_monitor_details`) — useful +for verifying after creation, but with no create or update path. +Note that the alert-rule tools return **legacy** rule IDs, which are not detector or +workflow IDs. ## Related diff --git a/src/skills/sentry-create-alert/SKILL.md b/src/skills/sentry-create-alert/SKILL.md deleted file mode 100644 index 7dd42115..00000000 --- a/src/skills/sentry-create-alert/SKILL.md +++ /dev/null @@ -1,216 +0,0 @@ ---- -name: sentry-create-alert -description: Create Sentry alerts using the workflow engine API. Use when asked to create alerts, set up notifications, configure issue priority alerts, or build workflow automations. Supports email, Slack, PagerDuty, Discord, and other notification actions. -license: Apache-2.0 ---- -# Create Sentry Alert - -Create alerts via Sentry’s workflow engine API. - -**Note:** This API is currently in **beta** and may be subject to change. -It is part of New Monitors and Alerts and may not be viewable in the legacy Alerts UI. - -## Invoke This Skill When - -- User asks to “create a Sentry alert” or “set up notifications” -- User wants to be emailed or notified when issues match certain conditions -- User mentions priority alerts, de-escalation alerts, or workflow automations -- User wants to configure Slack, PagerDuty, or email notifications for Sentry issues - -## Prerequisites - -- `curl` available in shell -- Sentry org auth token with `alerts:write` scope (also accepts `org:admin` or - `org:write`) - -## Phase 1: Gather Configuration - -Ask the user for any missing details: - -| Detail | Required | Example | -| --- | --- | --- | -| Org slug | Yes | `sentry`, `my-org` | -| Auth token | Yes | `sntryu_...` (needs `alerts:write` scope) | -| Region | Yes (default: `us`) | `us` → `us.sentry.io`, `de` → `de.sentry.io` | -| Alert name | Yes | `"High Priority De-escalation Alert"` | -| Trigger events | Yes | Which issue events fire the workflow | -| Conditions | Optional | Filter conditions before actions execute | -| Action type | Yes | `email`, `slack`, or `pagerduty` | -| Action target | Yes | User email, team, channel, or service | - -## Phase 2: Look Up IDs - -Use these API calls to resolve names to IDs as needed. - -```bash -API="https://{region}.sentry.io/api/0/organizations/{org}" -AUTH="Authorization: Bearer {token}" - -# Find user ID by email -curl -s "$API/members/" -H "$AUTH" | python3 -c " -import json,sys -for m in json.load(sys.stdin): - if m.get('email')=='USER_EMAIL' or m.get('user',{}).get('email')=='USER_EMAIL': - print(m['user']['id']); break" - -# List teams -curl -s "$API/teams/" -H "$AUTH" | python3 -c " -import json,sys -for t in json.load(sys.stdin): - print(t['id'], t['slug'])" - -# List integrations (for Slack/PagerDuty) -curl -s "$API/integrations/" -H "$AUTH" | python3 -c " -import json,sys -for i in json.load(sys.stdin): - print(i['id'], i['provider']['key'], i['name'])" -``` - -## Phase 3: Build Payload - -### Trigger Events - -Pick which issue events fire the workflow. -Use `logicType: "any-short"` (triggers must always use this). - -| Type | Fires when | -| --- | --- | -| `first_seen_event` | New issue created | -| `regression_event` | Resolved issue recurs | -| `reappeared_event` | Archived issue reappears | -| `issue_resolved_trigger` | Issue is resolved | - -### Filter Conditions - -Conditions that must pass before actions execute. -Use `logicType: "all"`, `"any-short"`, or `"none"`. - -**The `comparison` field is polymorphic** — its shape depends on the condition `type`: - -| Type | `comparison` format | Description | -| --- | --- | --- | -| `issue_priority_greater_or_equal` | `75` (bare integer) | Priority >= Low(25)/Medium(50)/High(75) | -| `issue_priority_deescalating` | `true` (bare boolean) | Priority dropped below peak | -| `event_frequency_count` | `{"value": 100, "interval": "1hr"}` | Event count in time window | -| `event_unique_user_frequency_count` | `{"value": 50, "interval": "1hr"}` | Affected users in time window | -| `tagged_event` | `{"key": "level", "match": "eq", "value": "error"}` | Event tag matches | -| `assigned_to` | `{"targetType": "Member", "targetIdentifier": 123}` | Issue assigned to target | -| `level` | `{"level": 40, "match": "gte"}` | Event level (fatal=50, error=40, warning=30) | -| `age_comparison` | `{"time": "hour", "value": 24, "comparisonType": "older"}` | Issue age | -| `issue_category` | `{"value": 1}` | Category (1=Error, 6=Feedback) | -| `issue_occurrences` | `{"value": 100}` | Total occurrence count | - -**Interval options:** `"1min"`, `"5min"`, `"15min"`, `"1hr"`, `"1d"`, `"1w"`, `"30d"` - -**Tag match types:** `"co"` (contains), `"nc"` (not contains), `"eq"`, `"ne"`, `"sw"` -(starts with), `"ew"` (ends with), `"is"` (set), `"ns"` (not set) - -Set `conditionResult` to `false` to invert (fire when condition is NOT met). - -### Actions - -| Type | Key Config | -| --- | --- | -| `email` | `config.targetType`: `"user"` / `"team"` / `"issue_owners"`, `config.targetIdentifier`: `` | -| `slack` | `integrationId`: ``, `config.targetDisplay`: `"#channel-name"` | -| `pagerduty` | `integrationId`: ``, `config.targetDisplay`: ``, `data.priority`: `"critical"` | -| `discord` | `integrationId`: ``, `data.tags`: tag list | -| `msteams` | `integrationId`: ``, `config.targetDisplay`: `` | -| `opsgenie` | `integrationId`: ``, `data.priority`: `"P1"`-`"P5"` | -| `jira` | `integrationId`: ``, `data`: project/issue config | -| `github` | `integrationId`: ``, `data`: repo/issue config | - -### Full Payload Structure - -```json -{ - "name": "", - "enabled": true, - "environment": null, - "config": { "frequency": 30 }, - "triggers": { - "logicType": "any-short", - "conditions": [ - { "type": "first_seen_event", "comparison": true, "conditionResult": true } - ], - "actions": [] - }, - "actionFilters": [{ - "logicType": "all", - "conditions": [ - { "type": "issue_priority_greater_or_equal", "comparison": 75, "conditionResult": true }, - { "type": "event_frequency_count", "comparison": {"value": 50, "interval": "1hr"}, "conditionResult": true } - ], - "actions": [{ - "type": "email", - "integrationId": null, - "data": {}, - "config": { - "targetType": "user", - "targetIdentifier": "", - "targetDisplay": null - }, - "status": "active" - }] - }] -} -``` - -`frequency`: minutes between repeated notifications. -Allowed values: `0`, `5`, `10`, `30`, `60`, `180`, `720`, `1440`. - -**Structure note:** `triggers.actions` is always `[]` — actions live inside -`actionFilters[].actions`. - -## Phase 4: Create the Alert - -```bash -curl -s -w "\n%{http_code}" -X POST \ - "https://{region}.sentry.io/api/0/organizations/{org}/workflows/" \ - -H "Authorization: Bearer {token}" \ - -H "Content-Type: application/json" \ - -d '{payload}' -``` - -Expect HTTP `201`. The response contains the workflow `id`. - -## Phase 5: Verify - -Confirm the alert was created and provide the UI link: - -``` -https://{org_slug}.sentry.io/monitors/alerts/{workflow_id}/ -``` - -If the org lacks the `workflow-engine-ui` feature flag, the alert appears at: - -``` -https://{org_slug}.sentry.io/alerts/rules/ -``` - -## Managing Alerts - -```bash -# List all workflows -curl -s "$API/workflows/" -H "$AUTH" - -# Get one workflow -curl -s "$API/workflows/{id}/" -H "$AUTH" - -# Update a workflow -curl -s -X PUT "$API/workflows/{id}/" -H "$AUTH" -H "Content-Type: application/json" -d '{payload}' - -# Delete a workflow -curl -s -X DELETE "$API/workflows/{id}/" -H "$AUTH" -# Expect 204 -``` - -## Troubleshooting - -| Issue | Solution | -| --- | --- | -| 401 Unauthorized | Token needs `alerts:write` scope | -| 403 Forbidden | Token must belong to the target org | -| 404 Not Found | Check org slug and region (`us` vs `de`) | -| 400 Bad Request | Validate payload JSON structure, check required fields | -| User ID not found | Verify email matches a member of the org | diff --git a/src/skills/sentry-create-monitor/SKILL.md b/src/skills/sentry-create-monitor/SKILL.md new file mode 100644 index 00000000..0e6bf615 --- /dev/null +++ b/src/skills/sentry-create-monitor/SKILL.md @@ -0,0 +1,337 @@ +--- +name: sentry-create-monitor +description: Create and edit Sentry monitors and the alerts that act on them — metric monitors with fixed, percentage-change, or anomaly-detection thresholds, uptime monitors, cron monitors, mobile app-size monitors, and alerts that notify Slack, email, PagerDuty, Discord, or open a Jira/GitHub ticket. Use when asked to monitor a metric or an endpoint's uptime, set up an alert or notification, change a threshold, route issues to a channel, or list and disable existing monitors and alerts. +license: Apache-2.0 +--- +# Create Sentry Monitors and Alerts + +Monitors decide when a signal becomes an issue. +Alerts decide what happens once it is one. + +**Read [`references/concepts/monitors.md`](references/concepts/monitors.md) first** — it is +the model this skill assumes, and getting the two stages backwards is the most common way +to build something that never fires. + +## Never hardcode the payload — read the live schema + +The payloads are large, polymorphic, and actively changing: the alert condition catalog +alone is ~20,000 characters, its shapes vary per condition `type`, and new types ship +without notice. + +So **before writing any payload, fetch the endpoint's reference page** and build the +request from what it says. +Any page becomes plain Markdown by appending `.md`: + +```bash +curl -sL https://docs.sentry.io/api/monitors.md # index of all 14 endpoints +curl -sL https://docs.sentry.io/api/monitors/create-an-alert-for-an-organization.md +curl -sL https://docs.sentry.io/api/monitors/create-a-monitor-for-a-project.md +``` + +Those last two are the only pages carrying real payload schema — the `dataSources` +recipes per metric, the detection types, and the condition and action catalogs. +This skill carries the procedure and the traps; the reference carries the fields. + +## Prerequisites + +- `curl` and `python3` (or `jq`). +- **A user auth token, not an organization auth token.** Create one under User settings → + Personal Tokens (`sntryu_…`) with `alerts:write`. + Org tokens (`sntrys_…`) authenticate as an anonymous user, and `GET /detectors/` plus + the bulk `PUT`/`DELETE` reject anonymous callers outright — while `POST /workflows/` + accepts them, so the failure looks arbitrary rather than like a bad token. + A `401` here means the wrong token type; a missing scope is a `403`. +- The org slug and its region host: `us.sentry.io`, `de.sentry.io`, or a self-hosted URL. + A 404 on a correct-looking path is usually the wrong region. + +```bash +API="https://us.sentry.io/api/0/organizations/" +AUTH="Authorization: Bearer $SENTRY_USER_TOKEN" +``` + +## Endpoint paths + +A Monitor is a `detector` in the API and an Alert is a `workflow` — the Naming section of +[`references/concepts/monitors.md`](references/concepts/monitors.md) covers why, and why +`/monitors/` is not the path you want: + +| Object | Path | +| --- | --- | +| Monitor (create) | `POST /organizations/{org}/projects/{project}/detectors/` | +| Monitor (list, bulk enable/disable, bulk delete) | `/organizations/{org}/detectors/` | +| Monitor (get, update, delete) | `/organizations/{org}/detectors/{id}/` | +| Alert (list, create, bulk enable/disable, bulk delete) | `/organizations/{org}/workflows/` | +| Alert (get, update, delete) | `/organizations/{org}/workflows/{id}/` | +| Legacy Crons API — do not use | `/organizations/{org}/monitors/` | + +This API is in **beta**. Expect fields to move, and re-read the reference page rather than +trusting a payload that worked last month. + +### Four undocumented endpoints worth knowing + +None appear in the reference, and each replaces a round of guessing: + +| Endpoint | Answers | +| --- | --- | +| `GET /detector-types/` | Which monitor types this org can create. Omits `monitor_check_in_failure` even though cron monitors are creatable | +| `GET /available-actions/` | Every action type with its installed integrations **and their services** — the only source for a PagerDuty service or Opsgenie team `targetIdentifier` | +| `POST /test-fire-actions/` | Fires an action against a sample event without saving anything, so a Slack or PagerDuty route can be proven before the alert exists. Body: `{"actions": [...], "projectSlug": "..."}`. Rate limited to 10/min | +| `GET /alert-rule-workflow/?alert_rule_id=` and `GET /alert-rule-detector/?alert_rule_id=` | Maps a **legacy** alert-rule ID to its workflow or detector ID. The MCP's `find_alert_rules` returns legacy IDs, which 404 against `/detectors/{id}/` | + +### Monitor types — all four go through `/detectors/` + +The reference documents `metric_issue` alone, which reads as though it is the only monitor +you can create. It isn't — the endpoint accepts any type registered with a validator: + +| Monitor | `type` | Build it with | +| --- | --- | --- | +| Metric | `metric_issue` | The reference page — `dataSources` (query), `config` (detection type), `conditionGroup` (thresholds) | +| Uptime | `uptime_domain_failure` | **The MCP, not this API** — see Playbook B | +| Cron | `monitor_check_in_failure` | One `dataSources` entry holding the monitor's `name`/`slug`, `owner`, and cron `config` (schedule, timezone, checkinMargin, maxRuntime, thresholds). The detector `config` is empty | +| Mobile builds | `preprod_size_analysis` | `dataSources` and `config` per its own validator | + +## Step 1 — Decide what the user actually needs + +Route before building. +The default monitors already exist, so a notification request usually needs **no monitor +at all**: + +| The user wants | What to build | +| --- | --- | +| “Tell me in Slack when a new issue appears” | An **alert** only — Playbook A | +| “Notify me when errors spike / latency crosses 800ms / this metric goes anomalous” | A **metric monitor**, then an alert — Playbook B | +| “Is my endpoint up?” | An **uptime monitor** — Playbook B | +| “My nightly job didn’t run” | A **cron monitor** — Playbook B, plus check-ins from code via `sentry-instrument` | +| “Tell me when the app binary grows” | A **mobile builds monitor** — Playbook B | +| “Change the threshold / add a channel / rename it” | An **edit** — Playbook C | +| “Turn this off”, “clean these up” | Playbook D | + +Confirm the target project and environment before writing anything. +Use the Sentry MCP (`find_organizations`, `find_projects`) if it is connected. + +## Step 2 — Resolve the IDs you will need + +Payloads reference users, teams, and integrations by ID, never by name. +`owner` takes the string form `user:` or `team:`; action targets take a bare ID. +Members and teams come from `$API/members/` and `$API/teams/`. + +For anything an action needs, use `available-actions` rather than `/integrations/` — it +returns the services within each integration, which `/integrations/` does not: + +```bash +curl -s "$API/available-actions/" -H "$AUTH" +``` + +If an integration the user asked for is absent, stop and tell them — it has to be +installed in Sentry first, and no payload can work around that. + +## Playbook A — Create an alert + +1. Fetch `create-an-alert-for-an-organization.md` and read the `triggers`, + `action_filters`, and `config` sections. + +2. List the monitors it should watch and note their IDs: + + ```bash + curl -s "$API/detectors/?project=" -H "$AUTH" | python3 -c " + import json,sys + for d in json.load(sys.stdin): print(d['id'], d['type'], repr(d['name']))" + ``` + + For “any new issue in this project”, connect that project’s `error` and `issue_stream` + monitors. + +3. Build the payload: `triggers` for the issue-state changes that fire it, `actionFilters` + for the conditions that must pass plus the actions to run, `detectorIds` for the + monitors it watches. + +4. Prove the notification route works before committing to it, via `test-fire-actions` + above. A misrouted Slack channel or PagerDuty service is otherwise invisible until a + real incident. + +5. POST it: + + ```bash + curl -s -w '\n%{http_code}\n' -X POST "$API/workflows/" \ + -H "$AUTH" -H 'Content-Type: application/json' -d @alert.json + ``` + + Expect `201`. The response contains the alert `id`. + +Keep alerts quiet enough to be trusted — filter to what genuinely deserves a +notification, and set `config.frequency` so a noisy issue does not page repeatedly. + +## Playbook B — Create a monitor + +**For an uptime monitor, use the MCP and skip the rest of this playbook.** +`create_uptime_monitor` takes the url, `intervalSeconds`, `timeoutMs`, method, headers, +thresholds, and owner directly — no token, no payload assembly, and the tool schema +states the legal interval values. `update_uptime_monitor`, `delete_uptime_monitor`, +`find_uptime_monitors`, and `get_uptime_monitor_details` cover the rest of its lifecycle, +the last one returning recent check results. +Fall back to `/detectors/` with `type: uptime_domain_failure` only if those tools are +absent. + +For the other types: + +1. Pick the `type` from the table above and get its payload shape — the reference page for + a metric monitor, or an existing object of that type for cron and mobile builds: + + ```bash + curl -s "$API/detectors/?query=type:monitor_check_in_failure" -H "$AUTH" + ``` + + **A response is not a request body.** Two fields differ: `dataSources` comes back + wrapped as `[{id, sourceId, type, queryObj: {…}}]` and the request wants the contents + of `queryObj` flat (for metric monitors, of `queryObj.snubaQuery`); and `owner` comes + back as an object but must be sent as `user:` / `team:`. + +2. Ground the threshold in real data before choosing it — query current values with + `search_events` over the MCP, or Discover — then pick the detection type: a fixed + threshold, a percentage change against a prior window, or dynamic anomaly detection + when the metric is seasonal or the normal range is unknown. + +3. Build `dataSources` (what to watch), `config` (how to detect), and, for metric + monitors, `conditionGroup`. Three rules the reference does not state: + + - A resolving condition (`conditionResult: 0`) is **required** unless the condition is + `anomaly_detection`. Omitting it fails with “Resolution condition required”. + - At most 3 conditions. + - `conditionResult` accepts only `75` (high), `50` (medium), and `0` (resolved). `25` + is rejected as “Unsupported condition result”, and the reference’s priority table + mislabels `50` as low. + +4. POST to the **project-scoped** path: + + ```bash + curl -s -w '\n%{http_code}\n' -X POST "$API/projects//detectors/" \ + -H "$AUTH" -H 'Content-Type: application/json' -d @monitor.json + ``` + +5. Check `enabled` on the response. Uptime and cron monitors consume a seat, and on an org + with none free they are created **disabled** instead of failing. + +6. Connect it to an alert, or it will open issues silently. Either set `workflowIds` on the + monitor or `detectorIds` on the alert — both describe the same link. Say which you did. + +7. For a cron monitor, wire the check-ins with **`sentry-instrument`**; the monitor cannot + report anything until the job does. + +## Playbook C — Edit a monitor or an alert + +**`PUT` merges — it does not replace.** Omitted top-level fields are left alone, so +`PUT {"enabled": false}` on a monitor is safe and complete (a monitor PUT needs neither +`name` nor `type`; an alert PUT requires `name`). + +**But every array you send is authoritative.** An `actionFilter`, `action`, or `condition` +that exists on the object and is missing from your array is **deleted** — +`"actionFilters": []` wipes all of them. To add a channel, echo the existing items **with +their `id`s** and append the new one **without** one. + +Two specific traps: + +- **Always send `enabled` explicitly on an alert PUT.** It defaults to `true`, so omitting + it silently re-enables an alert the user just turned off. +- Re-read the response-shape warning in Playbook B before echoing a GET back. + +```bash +curl -s "$API/detectors//" -H "$AUTH" # read current state +curl -s -w '\n%{http_code}\n' -X PUT "$API/detectors//" \ + -H "$AUTH" -H 'Content-Type: application/json' -d '{"enabled": false}' +``` + +Editing a **system-created** monitor — the default `error`, `issue_stream`, and +performance ones — needs `org:write`; `alerts:write` alone will not modify them, though it +is enough to connect one to an alert. + +Before overwriting anything a person configured, show the user the diff you intend to +apply. For read-only inspection the MCP needs no token: `find_alert_rules` and +`get_alert_rule` (legacy IDs — map them via `alert-rule-workflow` above), +`find_monitors` and `get_monitor_details` for cron, `find_uptime_monitors` and +`get_uptime_monitor_details` for uptime. + +## Playbook D — Enable, disable, delete + +Bulk `PUT`/`DELETE` on both collections **require** a filter — at least one of `id`, +`query`, `project`, or `projectSlug` — and 400 without one: + +```bash +curl -s -X PUT "$API/detectors/?id=" \ + -H "$AUTH" -H 'Content-Type: application/json' -d '{"enabled": false}' + +curl -s -w '\n%{http_code}\n' -X DELETE "$API/workflows//" -H "$AUTH" +``` + +A `DELETE` scoped by `project` fails wholesale with a `403`, because every project +contains undeletable system monitors. Enumerate first and delete by `?id=`. +Deletes are soft — objects move to `PENDING_DELETION` and stop appearing before they are +actually gone, so a disappearance is not proof of deletion. + +Deletes are destructive and a filter can match more than you expect. List the exact +objects a filter selects and get the user's confirmation before issuing one. + +## Wire-format traps + +- **`comparison` is polymorphic.** Its shape is dictated by the condition `type` — a bare + integer for a priority threshold, a bare boolean for a trigger, an object for a + frequency or tag condition. + Copy the shape from the reference page for that exact type. +- **`conditionResult` means different things per stage.** On an alert condition it is a + boolean — set `false` to invert. On a monitor's `conditionGroup` it is the issue + priority the threshold opens at, with `0` resolving the issue. +- **`triggers.actions` is always `[]`.** Actions live in `actionFilters[].actions`; the + trigger validator does not declare an `actions` field, so anything you put there is + dropped silently rather than stored and skipped. +- **`triggers.logicType` must be `any-short`** whenever the group contains a real trigger + condition. Filter groups additionally accept `all`, `any`, and `none`. +- **`config` on an alert takes `frequency` and nothing else** — any other key 400s. + `frequency` is any integer ≥ 0, in minutes; the value list on the reference page is the + UI's presets, not a constraint. +- **camelCase and snake_case both work** — the serializer converts the whole body + recursively, so the reference's snake_case names can be sent verbatim. What matters is + never sending both spellings of one key (that 400s with “collides with”), and that + errors always come back camelCased regardless of what you sent. + +## Step 3 — Verify + +A `201` on a create (or `200` on an update) means the payload parsed, not that it does +what was asked. Read the object back and confirm the conditions and actions match: + +```bash +curl -s "$API/workflows//" -H "$AUTH" +``` + +**Two ways an alert is silently dead**, neither of which produces an error: + +- It only evaluates issues from its **connected detectors**. A workflow with no + `detectorIds` is org-level and never fires. +- Its `environment`, if set, must match the event's exactly. Leave it `null` to match + every environment. + +Then surface the UI links: `https://.sentry.io/monitors/` for the list, +`/monitors//` for one monitor, `/monitors/alerts//` for one alert. +If the org lacks the new Monitors and Alerts UI, alerts appear under +`https://.sentry.io/alerts/rules/` instead. + +For a metric monitor, tell the user it only opens an issue once the threshold is genuinely +crossed — there is no test-fire for detection, so nothing appearing yet is expected rather +than a misconfiguration. + +## Troubleshooting + +| Symptom | Cause | +| --- | --- | +| 401 | Organization auth token — swap it for a user token (see Prerequisites) | +| 403 on a write | Token lacks `alerts:write`, or the target is a system-created monitor needing `org:write` | +| 403 on a bulk delete | The filter matched undeletable system monitors; delete by `?id=` | +| 404 on a valid path | Wrong region host, wrong slug, or a legacy alert-rule ID used as a detector ID | +| 400 “Resolution condition required” | Metric monitor `conditionGroup` has no `conditionResult: 0` | +| 400 “Unsupported condition result” | Priority other than `75`, `50`, `0` | +| 400 “collides with” | The same key sent in both camelCase and snake_case | +| 400 naming a field you sent | Re-read that field on the reference page; check the `comparison` trap | +| 400 on an unrecognized `type` | Not available to this org — `GET /detector-types/` lists what is | +| Update wiped actions or filters | An array you sent omitted existing items; echo them with their `id`s | +| Disabled alert came back on | `enabled` omitted from the PUT; it defaults to `true` | +| Monitor created but disabled | No seat available for an uptime or cron monitor | +| Monitor opens issues, nobody is notified | No alert connected — set `workflowIds` or `detectorIds` | +| Alert exists but never fires | No `detectorIds`, a mismatched `environment`, or a filter excluding everything | diff --git a/src/skills/sentry-create-monitor/references.yml b/src/skills/sentry-create-monitor/references.yml new file mode 100644 index 00000000..8cbe36b4 --- /dev/null +++ b/src/skills/sentry-create-monitor/references.yml @@ -0,0 +1,7 @@ +needs: + # The monitors -> issues -> alerts model this skill's whole procedure assumes, plus the + # cron doc it hands off to. Taken as the whole dir because the concept docs cross-link + # each other and hydration only places files on disk; SKILL.md reads monitors.md. + - concepts/*.md + # Metric monitor data sources and alert filters both take a Sentry search query. + - search-query-language.md diff --git a/src/skills/sentry-get-started/SKILL.md b/src/skills/sentry-get-started/SKILL.md index 4bdbf68e..d151c9a3 100644 --- a/src/skills/sentry-get-started/SKILL.md +++ b/src/skills/sentry-get-started/SKILL.md @@ -81,8 +81,9 @@ flag it. You’ll also want to immediately read and the baseline-signal context in hand before you start. When it’s done, surface other options — chiefly the **`sentry-instrument`** skill to add -more telemetry (logging, profiling, session replay, crons, …), and releases so issues -tie to the deploy that introduced them. +more telemetry (logging, profiling, session replay, crons, …), releases so issues tie to +the deploy that introduced them, and the **`sentry-create-monitor`** skill so someone +actually gets notified when an issue shows up. As in the existing-user path, only name a skill you’ve confirmed is available in your harness’s skill list; otherwise offer the docs fallback. Don’t auto-run them. @@ -112,8 +113,13 @@ what they want: → the **`sentry-setup-releases`** skill, or do it here from [`references/releases/index.md`](references/releases/index.md); the `release`/`environment` tag in particular belongs in setup itself. -- **Improve / harden** (scrubbing, volume, OTel) and **Monitors & alerts** → not built - as skills yet; be honest and offer to read through the docs. +- **Monitors and alerts** — put a threshold on a metric, or notify Slack / email / + PagerDuty when issues match. + → the **`sentry-create-monitor`** skill. +- **Send OpenTelemetry to Sentry** — Collector pipelines and multi-project routing. + → the **`sentry-otel-exporter-setup`** skill. +- **Improve / harden** (scrubbing, volume) → not built as skills yet; be honest and + offer to read through the docs. ## Honesty about coverage From dfc2274c1225342bb5146d87c3e2df6e87ca1fc2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:09:27 +0000 Subject: [PATCH 2/2] style: apply prek fixes --- src/skills/sentry-create-monitor/SKILL.md | 148 ++++++++++++---------- 1 file changed, 78 insertions(+), 70 deletions(-) diff --git a/src/skills/sentry-create-monitor/SKILL.md b/src/skills/sentry-create-monitor/SKILL.md index 0e6bf615..89ffb86b 100644 --- a/src/skills/sentry-create-monitor/SKILL.md +++ b/src/skills/sentry-create-monitor/SKILL.md @@ -8,9 +8,9 @@ license: Apache-2.0 Monitors decide when a signal becomes an issue. Alerts decide what happens once it is one. -**Read [`references/concepts/monitors.md`](references/concepts/monitors.md) first** — it is -the model this skill assumes, and getting the two stages backwards is the most common way -to build something that never fires. +**Read [`references/concepts/monitors.md`](references/concepts/monitors.md) first** — it +is the model this skill assumes, and getting the two stages backwards is the most common +way to build something that never fires. ## Never hardcode the payload — read the live schema @@ -18,7 +18,7 @@ The payloads are large, polymorphic, and actively changing: the alert condition alone is ~20,000 characters, its shapes vary per condition `type`, and new types ship without notice. -So **before writing any payload, fetch the endpoint's reference page** and build the +So **before writing any payload, fetch the endpoint’s reference page** and build the request from what it says. Any page becomes plain Markdown by appending `.md`: @@ -35,14 +35,14 @@ This skill carries the procedure and the traps; the reference carries the fields ## Prerequisites - `curl` and `python3` (or `jq`). -- **A user auth token, not an organization auth token.** Create one under User settings → - Personal Tokens (`sntryu_…`) with `alerts:write`. - Org tokens (`sntrys_…`) authenticate as an anonymous user, and `GET /detectors/` plus - the bulk `PUT`/`DELETE` reject anonymous callers outright — while `POST /workflows/` - accepts them, so the failure looks arbitrary rather than like a bad token. +- **A user auth token, not an organization auth token.** Create one under User settings + → Personal Tokens (`sntryu_…`) with `alerts:write`. Org tokens (`sntrys_…`) + authenticate as an anonymous user, and `GET /detectors/` plus the bulk `PUT`/`DELETE` + reject anonymous callers outright — while `POST /workflows/` accepts them, so the + failure looks arbitrary rather than like a bad token. A `401` here means the wrong token type; a missing scope is a `403`. -- The org slug and its region host: `us.sentry.io`, `de.sentry.io`, or a self-hosted URL. - A 404 on a correct-looking path is usually the wrong region. +- The org slug and its region host: `us.sentry.io`, `de.sentry.io`, or a self-hosted + URL. A 404 on a correct-looking path is usually the wrong region. ```bash API="https://us.sentry.io/api/0/organizations/" @@ -51,9 +51,9 @@ AUTH="Authorization: Bearer $SENTRY_USER_TOKEN" ## Endpoint paths -A Monitor is a `detector` in the API and an Alert is a `workflow` — the Naming section of -[`references/concepts/monitors.md`](references/concepts/monitors.md) covers why, and why -`/monitors/` is not the path you want: +A Monitor is a `detector` in the API and an Alert is a `workflow` — the Naming section +of [`references/concepts/monitors.md`](references/concepts/monitors.md) covers why, and +why `/monitors/` is not the path you want: | Object | Path | | --- | --- | @@ -64,8 +64,8 @@ A Monitor is a `detector` in the API and an Alert is a `workflow` — the Naming | Alert (get, update, delete) | `/organizations/{org}/workflows/{id}/` | | Legacy Crons API — do not use | `/organizations/{org}/monitors/` | -This API is in **beta**. Expect fields to move, and re-read the reference page rather than -trusting a payload that worked last month. +This API is in **beta**. Expect fields to move, and re-read the reference page rather +than trusting a payload that worked last month. ### Four undocumented endpoints worth knowing @@ -76,18 +76,19 @@ None appear in the reference, and each replaces a round of guessing: | `GET /detector-types/` | Which monitor types this org can create. Omits `monitor_check_in_failure` even though cron monitors are creatable | | `GET /available-actions/` | Every action type with its installed integrations **and their services** — the only source for a PagerDuty service or Opsgenie team `targetIdentifier` | | `POST /test-fire-actions/` | Fires an action against a sample event without saving anything, so a Slack or PagerDuty route can be proven before the alert exists. Body: `{"actions": [...], "projectSlug": "..."}`. Rate limited to 10/min | -| `GET /alert-rule-workflow/?alert_rule_id=` and `GET /alert-rule-detector/?alert_rule_id=` | Maps a **legacy** alert-rule ID to its workflow or detector ID. The MCP's `find_alert_rules` returns legacy IDs, which 404 against `/detectors/{id}/` | +| `GET /alert-rule-workflow/?alert_rule_id=` and `GET /alert-rule-detector/?alert_rule_id=` | Maps a **legacy** alert-rule ID to its workflow or detector ID. The MCP’s `find_alert_rules` returns legacy IDs, which 404 against `/detectors/{id}/` | ### Monitor types — all four go through `/detectors/` -The reference documents `metric_issue` alone, which reads as though it is the only monitor -you can create. It isn't — the endpoint accepts any type registered with a validator: +The reference documents `metric_issue` alone, which reads as though it is the only +monitor you can create. +It isn’t — the endpoint accepts any type registered with a validator: | Monitor | `type` | Build it with | | --- | --- | --- | | Metric | `metric_issue` | The reference page — `dataSources` (query), `config` (detection type), `conditionGroup` (thresholds) | | Uptime | `uptime_domain_failure` | **The MCP, not this API** — see Playbook B | -| Cron | `monitor_check_in_failure` | One `dataSources` entry holding the monitor's `name`/`slug`, `owner`, and cron `config` (schedule, timezone, checkinMargin, maxRuntime, thresholds). The detector `config` is empty | +| Cron | `monitor_check_in_failure` | One `dataSources` entry holding the monitor’s `name`/`slug`, `owner`, and cron `config` (schedule, timezone, checkinMargin, maxRuntime, thresholds). The detector `config` is empty | | Mobile builds | `preprod_size_analysis` | `dataSources` and `config` per its own validator | ## Step 1 — Decide what the user actually needs @@ -138,12 +139,12 @@ installed in Sentry first, and no payload can work around that. for d in json.load(sys.stdin): print(d['id'], d['type'], repr(d['name']))" ``` - For “any new issue in this project”, connect that project’s `error` and `issue_stream` - monitors. + For “any new issue in this project”, connect that project’s `error` and + `issue_stream` monitors. -3. Build the payload: `triggers` for the issue-state changes that fire it, `actionFilters` - for the conditions that must pass plus the actions to run, `detectorIds` for the - monitors it watches. +3. Build the payload: `triggers` for the issue-state changes that fire it, + `actionFilters` for the conditions that must pass plus the actions to run, + `detectorIds` for the monitors it watches. 4. Prove the notification route works before committing to it, via `test-fire-actions` above. A misrouted Slack channel or PagerDuty service is otherwise invisible until a @@ -166,16 +167,17 @@ notification, and set `config.frequency` so a noisy issue does not page repeated **For an uptime monitor, use the MCP and skip the rest of this playbook.** `create_uptime_monitor` takes the url, `intervalSeconds`, `timeoutMs`, method, headers, thresholds, and owner directly — no token, no payload assembly, and the tool schema -states the legal interval values. `update_uptime_monitor`, `delete_uptime_monitor`, -`find_uptime_monitors`, and `get_uptime_monitor_details` cover the rest of its lifecycle, -the last one returning recent check results. +states the legal interval values. +`update_uptime_monitor`, `delete_uptime_monitor`, `find_uptime_monitors`, and +`get_uptime_monitor_details` cover the rest of its lifecycle, the last one returning +recent check results. Fall back to `/detectors/` with `type: uptime_domain_failure` only if those tools are absent. For the other types: -1. Pick the `type` from the table above and get its payload shape — the reference page for - a metric monitor, or an existing object of that type for cron and mobile builds: +1. Pick the `type` from the table above and get its payload shape — the reference page + for a metric monitor, or an existing object of that type for cron and mobile builds: ```bash curl -s "$API/detectors/?query=type:monitor_check_in_failure" -H "$AUTH" @@ -194,12 +196,12 @@ For the other types: 3. Build `dataSources` (what to watch), `config` (how to detect), and, for metric monitors, `conditionGroup`. Three rules the reference does not state: - - A resolving condition (`conditionResult: 0`) is **required** unless the condition is - `anomaly_detection`. Omitting it fails with “Resolution condition required”. + - A resolving condition (`conditionResult: 0`) is **required** unless the condition + is `anomaly_detection`. Omitting it fails with “Resolution condition required”. - At most 3 conditions. - - `conditionResult` accepts only `75` (high), `50` (medium), and `0` (resolved). `25` - is rejected as “Unsupported condition result”, and the reference’s priority table - mislabels `50` as low. + - `conditionResult` accepts only `75` (high), `50` (medium), and `0` (resolved). + `25` is rejected as “Unsupported condition result”, and the reference’s priority + table mislabels `50` as low. 4. POST to the **project-scoped** path: @@ -208,14 +210,16 @@ For the other types: -H "$AUTH" -H 'Content-Type: application/json' -d @monitor.json ``` -5. Check `enabled` on the response. Uptime and cron monitors consume a seat, and on an org - with none free they are created **disabled** instead of failing. +5. Check `enabled` on the response. + Uptime and cron monitors consume a seat, and on an org with none free they are + created **disabled** instead of failing. -6. Connect it to an alert, or it will open issues silently. Either set `workflowIds` on the - monitor or `detectorIds` on the alert — both describe the same link. Say which you did. +6. Connect it to an alert, or it will open issues silently. + Either set `workflowIds` on the monitor or `detectorIds` on the alert — both describe + the same link. Say which you did. -7. For a cron monitor, wire the check-ins with **`sentry-instrument`**; the monitor cannot - report anything until the job does. +7. For a cron monitor, wire the check-ins with **`sentry-instrument`**; the monitor + cannot report anything until the job does. ## Playbook C — Edit a monitor or an alert @@ -223,15 +227,16 @@ For the other types: `PUT {"enabled": false}` on a monitor is safe and complete (a monitor PUT needs neither `name` nor `type`; an alert PUT requires `name`). -**But every array you send is authoritative.** An `actionFilter`, `action`, or `condition` -that exists on the object and is missing from your array is **deleted** — -`"actionFilters": []` wipes all of them. To add a channel, echo the existing items **with -their `id`s** and append the new one **without** one. +**But every array you send is authoritative.** An `actionFilter`, `action`, or +`condition` that exists on the object and is missing from your array is **deleted** — +`"actionFilters": []` wipes all of them. +To add a channel, echo the existing items **with their `id`s** and append the new one +**without** one. Two specific traps: -- **Always send `enabled` explicitly on an alert PUT.** It defaults to `true`, so omitting - it silently re-enables an alert the user just turned off. +- **Always send `enabled` explicitly on an alert PUT.** It defaults to `true`, so + omitting it silently re-enables an alert the user just turned off. - Re-read the response-shape warning in Playbook B before echoing a GET back. ```bash @@ -241,8 +246,8 @@ curl -s -w '\n%{http_code}\n' -X PUT "$API/detectors//" \ ``` Editing a **system-created** monitor — the default `error`, `issue_stream`, and -performance ones — needs `org:write`; `alerts:write` alone will not modify them, though it -is enough to connect one to an alert. +performance ones — needs `org:write`; `alerts:write` alone will not modify them, though +it is enough to connect one to an alert. Before overwriting anything a person configured, show the user the diff you intend to apply. For read-only inspection the MCP needs no token: `find_alert_rules` and @@ -263,34 +268,37 @@ curl -s -w '\n%{http_code}\n' -X DELETE "$API/workflows//" -H "$AUTH" ``` A `DELETE` scoped by `project` fails wholesale with a `403`, because every project -contains undeletable system monitors. Enumerate first and delete by `?id=`. -Deletes are soft — objects move to `PENDING_DELETION` and stop appearing before they are -actually gone, so a disappearance is not proof of deletion. +contains undeletable system monitors. +Enumerate first and delete by `?id=`. Deletes are soft — objects move to +`PENDING_DELETION` and stop appearing before they are actually gone, so a disappearance +is not proof of deletion. -Deletes are destructive and a filter can match more than you expect. List the exact -objects a filter selects and get the user's confirmation before issuing one. +Deletes are destructive and a filter can match more than you expect. +List the exact objects a filter selects and get the user’s confirmation before issuing +one. ## Wire-format traps -- **`comparison` is polymorphic.** Its shape is dictated by the condition `type` — a bare - integer for a priority threshold, a bare boolean for a trigger, an object for a +- **`comparison` is polymorphic.** Its shape is dictated by the condition `type` — a + bare integer for a priority threshold, a bare boolean for a trigger, an object for a frequency or tag condition. Copy the shape from the reference page for that exact type. - **`conditionResult` means different things per stage.** On an alert condition it is a - boolean — set `false` to invert. On a monitor's `conditionGroup` it is the issue - priority the threshold opens at, with `0` resolving the issue. + boolean — set `false` to invert. + On a monitor’s `conditionGroup` it is the issue priority the threshold opens at, with + `0` resolving the issue. - **`triggers.actions` is always `[]`.** Actions live in `actionFilters[].actions`; the trigger validator does not declare an `actions` field, so anything you put there is dropped silently rather than stored and skipped. -- **`triggers.logicType` must be `any-short`** whenever the group contains a real trigger - condition. Filter groups additionally accept `all`, `any`, and `none`. +- **`triggers.logicType` must be `any-short`** whenever the group contains a real + trigger condition. Filter groups additionally accept `all`, `any`, and `none`. - **`config` on an alert takes `frequency` and nothing else** — any other key 400s. - `frequency` is any integer ≥ 0, in minutes; the value list on the reference page is the - UI's presets, not a constraint. + `frequency` is any integer ≥ 0, in minutes; the value list on the reference page is + the UI’s presets, not a constraint. - **camelCase and snake_case both work** — the serializer converts the whole body - recursively, so the reference's snake_case names can be sent verbatim. What matters is - never sending both spellings of one key (that 400s with “collides with”), and that - errors always come back camelCased regardless of what you sent. + recursively, so the reference’s snake_case names can be sent verbatim. + What matters is never sending both spellings of one key (that 400s with “collides + with”), and that errors always come back camelCased regardless of what you sent. ## Step 3 — Verify @@ -305,17 +313,17 @@ curl -s "$API/workflows//" -H "$AUTH" - It only evaluates issues from its **connected detectors**. A workflow with no `detectorIds` is org-level and never fires. -- Its `environment`, if set, must match the event's exactly. Leave it `null` to match - every environment. +- Its `environment`, if set, must match the event’s exactly. + Leave it `null` to match every environment. Then surface the UI links: `https://.sentry.io/monitors/` for the list, `/monitors//` for one monitor, `/monitors/alerts//` for one alert. If the org lacks the new Monitors and Alerts UI, alerts appear under `https://.sentry.io/alerts/rules/` instead. -For a metric monitor, tell the user it only opens an issue once the threshold is genuinely -crossed — there is no test-fire for detection, so nothing appearing yet is expected rather -than a misconfiguration. +For a metric monitor, tell the user it only opens an issue once the threshold is +genuinely crossed — there is no test-fire for detection, so nothing appearing yet is +expected rather than a misconfiguration. ## Troubleshooting