error-scan
Scan Sentry for new/regressed unresolved issues, dynamically map them to repos. Report only — no code changes.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Scan Sentry for new/regressed unresolved issues, dynamically map them to repos. Report only — no code changes.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Query and manage Shipwright agents via the admin API — cron jobs, env vars, tool permissions, API tokens, and plugins. Use to configure the running agent, manage its schedules, inspect another agent's config, or provision a new agent.
Orchestrates the full test-readiness pipeline (phases 1–5), once per qualifying repo under repos/, each in its own worktree + branch. Runs test-inventory → test-design → test-migration → test-roadmap → test-fix in sequence per repo, starting from the first stale artifact. Invoked by the `shipwright-test-readiness` cron on a daily schedule; exits early for a repo when all of its phase artifacts are fresh.
Cross-cutting contract for the test-readiness pipeline. Specifies the GitHub repository configuration that makes "tests pass" structurally meaningful instead of advisory — branch protection requiring CI to pass, required secrets, environments, deploy hooks, and PR templates. Referenced by `test-design` (Phase 2 emits the repo-config plan) and `test-roadmap` (Phase 4 auto-pairs workflow tasks with branch-protection tasks). Without these, the verification commands in each task body are honor-system; with them, the workflow is the gate.
Phase 4 of the test-readiness pipeline. Synthesizes the three prior artifacts (inventory, system design, migration) into a single executable roadmap `test-readiness-plan.md` with five sequenced milestones and an agent-executable task list. Includes mandatory sections on where we are now, where we want to be, the gap, the speed delta, and open risks. Output is suitable for handoff to an engineer or to `shipwright /dev-task`. Invoke when the `/test-roadmap` command runs.
Query and update the Shipwright task store — pick the next ready task, mark status transitions, and append new tasks. Use whenever you need to read from or write to the task queue. Calls the task store HTTP API directly via curl.
This skill MUST activate whenever creating, editing, or reviewing ANY Shipwright Harness marketing or brand artifact — e.g. "make a Shipwright one-pager", "create a Shipwright Harness slide deck", "design the OG / social image", "build an on-brand landing-page section", "Shipwright marketing material", "is this on-brand?", "generate a Shipwright PDF/deck", or when editing anything under `brand/`, the marketing website, or `brand/templates/`. It enforces the locked design system in `brand/BRAND.md` + `brand/tokens.json` so every artifact (website, one-pager, deck, video slides, social card, PDF) is on-brand by construction, not by memory.
| name | error-scan |
| description | Scan Sentry for new/regressed unresolved issues, dynamically map them to repos. Report only — no code changes. |
Query Sentry for currently-unresolved issues across the org, diff them against a local
ledger to find what's new or regressed since the last run, dynamically derive which
checked-out repo each issue's service tag belongs to, and write a structured report. This
skill makes no code changes — it reads and reports only. Use /error-fix to act on the
findings.
This skill hardcodes Sentry as the backend (no provider abstraction) — see
planning/error-patrol/PLAN.md for the rationale. Support for other error-reporting
backends is explicitly out of scope.
Before starting, check if any flags were passed:
--summary — print counts to stdout; skip writing error-report.md--dry-run — run the full scan (Sentry API calls, service→repo derivation, diffing) but
skip writing error-report.md and skip updating state/error-patrol-ledger.json.
Print everything that would have been written to stdout instead. Use this to validate the
skill end-to-end without mutating any state.SENTRY_ORG and SENTRY_AUTH_TOKEN are set in the environment (echo "org=$SENTRY_ORG token_set=$([ -n "$SENTRY_AUTH_TOKEN" ] && echo yes || echo no)"). If
either is unset or empty, print:
error-scan requires SENTRY_ORG and SENTRY_AUTH_TOKEN to be set in the environment. Skipping scan.
and stop. Do not write a report or touch the ledger.SHIPWRIGHT_REPO_DIR is set (fall back to $HOME/src per the plugin default —
see docs/configuration.md). This is the directory containing whatever repo checkouts
the invoking agent happens to have — never assume specific repo names live here.curl -sS -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" "https://sentry.io/api/0/..."
Never print $SENTRY_AUTH_TOKEN itself, never write it to any file, and never echo the
literal value of $SENTRY_ORG into a comment or log line that could get pasted somewhere
persistent — always reference these as their env var names in output and in this file.curl -sS -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
"https://sentry.io/api/0/organizations/$SENTRY_ORG/projects/"
slug field (e.g.
via jq -r '.[].slug'). This is a paginated endpoint — Sentry returns a Link response
header with rel="next" / results="true|false"; follow pagination (via the cursor
query param from the Link header) until results="false" to get the full project list.No Sentry projects found for org $SENTRY_ORG. and
stop (nothing to scan).service Tag Values (Dynamic — No Hardcoded Service List)For each project slug from Step 1, call the tag-values endpoint to discover which service
tag values are currently reporting:
curl -sS -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
"https://sentry.io/api/0/projects/$SENTRY_ORG/$PROJECT_SLUG/tags/service/values/"
value field (e.g. via jq -r '.[].value'). Follow pagination the same way as Step 1 if present.service tag at all (404 or empty array), that's a valid result —
just means no events on that project carry a service tag. Move to the next project.service values across all projects into one deduplicated set — this is the
full list of "observed service tags" for this run. Do not hardcode any service name; this
set is entirely derived from the API responses above.No service tag values found across any project. and stop (nothing to map or diff).This mapping tells a downstream skill (error-fix) which repo checkout corresponds to each
service tag, so it knows where to look for the code that raised an issue. Getting this wrong
sends a future agent session into the wrong source tree, so this step is deliberately
conservative: when in doubt, mark unmapped — never guess.
Do not hardcode any repo name anywhere in this step's logic. The set of repos is
whatever happens to exist under $SHIPWRIGHT_REPO_DIR on this run — treat it as fully
dynamic, the same way the Sentry projects and service tags are dynamic.
List the repos currently checked out under $SHIPWRIGHT_REPO_DIR:
find "$SHIPWRIGHT_REPO_DIR" -mindepth 1 -maxdepth 1 -type d
Each top-level directory here is a candidate repo. If $SHIPWRIGHT_REPO_DIR doesn't
exist or is empty, treat every service tag from Step 2 as unmapped (there's nothing to
grep) and continue — this is not a fatal error, just an empty mapping.
For each service tag value S from Step 2, and for each candidate repo directory R
from step 1, grep R for the literal quoted string that would appear in a Sentry init
call site, e.g. "S" or 'S' (grep for the tag value as an exact quoted literal, not a
substring match — a service value of admin should not match a file merely containing
the word "administrator"). Use something like:
grep -rn --include='*.ts' --include='*.js' --include='*.py' --include='*.go' --include='*.rb' \
-e "\"$S\"" -e "'$S'" "$R"
(adjust the --include extensions to whatever source languages are actually present in
R — don't assume TypeScript specifically).
For every match, inspect a small window of context around the matched line (a few lines
before/after — grep -B3 -A1 or read the surrounding lines with the Read tool). Treat a
match as a confident hit only if that context also contains something that looks like
a Sentry init/config call site: the word sentry or Sentry and a service key
(e.g. service: or service = or service:"...") appearing near the matched literal.
A bare string match with no nearby "sentry"/"service" signal is not confident — it's
just an incidental match and must be discarded, not counted as a hit for that repo.
After checking every candidate repo for service tag S, classify the result:
S → that repo's directory name.
Record it as mapped.unmapped (reason: "no confident match").unmapped (reason: "ambiguous — matched in N repos", listing which repos matched). Do not pick one arbitrarily.node_modules,
dist, build, vendor) does not count — exclude these paths from consideration
before grepping if practical (e.g. add --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=vendor to the grep).Build the mapping as a simple object, e.g.:
{
"<observed service tag A>": { "status": "mapped", "repo": "<matched repo dir name>", "matchedAt": "<path/to/file>:<line>" },
"<observed service tag B>": { "status": "unmapped", "reason": "no confident match" }
}
(the keys and values above are placeholders illustrating the shape only — do not copy any of them as literal defaults; every entry must come from this run's actual grep results against the actual service tags and repos discovered in Steps 1-3.)
This mapping is re-derived from scratch every run. Never read a previously-persisted mapping from the ledger and trust it as-is — Step 6 persists this run's freshly-derived map for human visibility only, not as an input to this step.
For each project slug from Step 1, fetch unresolved issues:
curl -sS -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
"https://sentry.io/api/0/organizations/$SENTRY_ORG/issues/?query=is:unresolved&project=$PROJECT_ID"
Notes on this endpoint:
project id (not slug) as a filter, or can
be queried without a project filter to get issues across every project the token can
see in one call — prefer the no-project-filter form (.../organizations/$SENTRY_ORG/issues/?query=is:unresolved)
if it returns results across all projects in this Sentry instance; fall back to per-project
calls (resolving each project's numeric id from the Step 1 response) only if the
org-wide call appears scoped to a single default project.Link header / cursor param as in Step 1, so large result sets
aren't silently truncated.id, shortId, title, culprit, permalink,
status (should be unresolved given the query filter), count (total event count),
userCount, firstSeen, lastSeen, and the project's slug.service by reading the issue's service tag if present in
the list payload; if not present in the list response, fetch
https://sentry.io/api/0/organizations/$SENTRY_ORG/issues/$ISSUE_ID/tags/service/ for that
issue to resolve it. If an issue has no service tag at all, record its service as null
(it will show up in the report as unmapped by definition — there's nothing to map).The ledger lives at state/error-patrol-ledger.json — one level up from the repo
checkout, in agent workspace state, not tracked inside this repo (same tier as
state/entropy-patrol-last-run.json; see that file for an example of this state tier's
location convention). Resolve its path relative to the repo root the same way
state/entropy-patrol-last-run.json sits relative to repos/<repo> — i.e. it is a sibling
of the repo checkouts, not a path inside the invoking repo's working tree.
state/error-patrol-ledger.json does not exist yet, treat the ledger as empty
({"issues": {}, "lastRun": null, "serviceRepoMap": {}}) — this is a normal first run,
not an error.{
"lastRun": "<ISO8601 timestamp of previous run>",
"issues": {
"<sentry_issue_id>": {
"status": "unresolved | resolved | ignored",
"count": <last-seen event count as of previous run>,
"lastSeen": "<ISO8601>"
}
},
"serviceRepoMap": { "<service>": { "status": "mapped|unmapped", "repo": "...", ... } }
}
issues
entry for that issue's id:
id in the ledger at all.status for
that issue was resolved or ignored (i.e. it was not unresolved last run, and is
unresolved now), or (b) the ledger's recorded status was already unresolved but
the current count is greater than the ledger's recorded count (i.e. it kept
firing — more events accumulated since last run). Document this precisely in the
report so a human reviewing it understands why an issue was flagged regressed.unresolved, and current count
is not greater than the ledger's recorded count. Unchanged issues are not included
in the report body (only in the summary counts, if you choose to show a total).unresolved but no longer appear in this run's
unresolved fetch (Step 4) have presumably been resolved/ignored since the last run —
note this for the ledger update in Step 6, but they do not need their own report section
(that's error-resolve's concern, not this skill's).If --summary or --dry-run was passed, skip writing the file — for --dry-run, print the
exact content that would have been written instead; for --summary, print only the counts
table from below.
Write error-report.md to the project root (overwrite if it exists — same convention as
entropy-report.md). Format:
# Error Report
**Generated:** {YYYY-MM-DD HH:MM} {timezone}
**Org:** $SENTRY_ORG (read from env — value not repeated literally in this file)
**Projects scanned:** {count}
**Service tags observed:** {count}
## Summary
| | Count |
|---|---|
| New issues | N |
| Regressed issues | N |
| Unchanged (not shown below) | N |
| Unmapped service tags | N |
---
## New Issues
{If none: "No new issues since last run."}
{For each new issue, as a checkbox:}
- [ ] `{shortId}` — {title} _{service tag, or "unmapped" if no confident repo match}_
- Project: {project slug}
- Repo: {mapped repo dir name, or **UNMAPPED** with the reason from Step 3}
- Events: {count} · Users affected: {userCount}
- First seen: {firstSeen} · Last seen: {lastSeen}
- Link: {permalink}
---
## Regressed Issues
{If none: "No regressed issues since last run."}
{For each regressed issue, as a checkbox:}
- [ ] `{shortId}` — {title} _{service tag, or "unmapped"}_
- Project: {project slug}
- Repo: {mapped repo dir name, or **UNMAPPED** with the reason}
- Why flagged: {"was resolved/ignored, now unresolved" | "event count grew from {old} to {new}"}
- Events: {count} · Users affected: {userCount}
- Last seen: {lastSeen}
- Link: {permalink}
---
## Service → Repo Mapping (this run)
| Service tag | Status | Repo | Detail |
|---|---|---|---|
| {service} | mapped | {repo dir name} | matched at `{file:line}` |
| {service} | **unmapped** | — | {reason, e.g. "no confident match" or "ambiguous — matched in N repos"} |
_Any issue whose service tag is unmapped above is flagged **UNMAPPED** in its own entry too — `/error-fix` should treat these as needing manual repo identification, never a guessed repo._
---
_Run `/error-fix` to classify these issues and queue task-store tasks._
Rules:
count descending (highest event
volume first) within their section.Skip this entire step if --dry-run was passed (dry runs must not mutate any state).
--summary alone does not skip this step — summary mode only affects report output, not
ledger persistence, since keeping the ledger current is what makes the next run's diff
correct.
{
"lastRun": "<current UTC ISO-8601>",
"issues": {
"<sentry_issue_id>": {
"status": "unresolved",
"count": <current event count>,
"lastSeen": "<ISO8601 from the issue payload>"
}
// one entry per issue fetched in Step 4 (all currently-unresolved issues, not just new/regressed ones)
},
"serviceRepoMap": {
"<service tag>": { "status": "mapped", "repo": "<repo dir name>", "matchedAt": "<file:line>" }
// or: { "status": "unmapped", "reason": "<reason from Step 3>" }
// one entry per service tag observed in Step 2, from this run's fresh derivation
}
}
issues map but absent from this run's
unresolved fetch (Step 4), you may either drop it or retain it with "status": "resolved"
— retaining it lets error-resolve observe the transition; prefer retaining with
"status": "resolved" over dropping, since dropping loses the "this used to be
unresolved" signal for no benefit.state/error-patrol-ledger.json with this new content (this is a full
replace, not an append — unlike entropy-scan's quality log, which is append-only, this
ledger is a current-state snapshot).Ledger updated: state/error-patrol-ledger.jsonImportant: serviceRepoMap in the ledger is written every run from this run's fresh
Step 3 derivation — it is overwritten, never merged with the previous run's map, and it is
never read back in as an input to Step 3. Its only purpose is letting a human (or
error-fix) see what the last run inferred, without re-running the grep themselves.
Whether or not --summary was passed, always print a summary to stdout after the scan:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ERROR SCAN COMPLETE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Projects scanned: {N}
Service tags observed: {N}
Services mapped: {N}
Services unmapped: {N}
NEW {N} issues
REGRESSED {N} issues
─────────────────────
Unchanged (not reported): {N}
{If any new/regressed issues exist:}
Run /error-fix to classify these issues and queue task-store tasks.
{If zero new/regressed issues:}
✓ No new or regressed issues since last run.
{If --dry-run: "Dry run — no files written."}
{Else if --summary: "Summary only — error-report.md not written; ledger still updated."}
{Else: "Report written to: error-report.md"}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
error-report.md (project root) and state/error-patrol-ledger.json (agent workspace
state, one level up from repo checkouts) — and neither is written in --dry-run mode.error-scan never creates PRs or tasks — queueing issues as
task-store tasks belongs to /error-fix.GET requests against the Sentry API. Never call an
issue-mutating endpoint (resolve/ignore/delete) from this skill — that's /error-resolve's
job.$SENTRY_ORG is read from the
environment at runtime and never written literally into this file, into error-report.md,
or into the ledger. Sentry projects and service tag values are always enumerated via the
API. Repos are always enumerated by listing $SHIPWRIGHT_REPO_DIR at runtime — never
assume or name a specific repo directory in this skill's logic.$SENTRY_AUTH_TOKEN. Not in the report, not in the ledger, not
in stdout output.unmapped — never resolved to a "best guess"
repo. A wrong mapping here would send /error-fix to edit the wrong codebase.error-fix visibility only — it is not read back
in as an input to a future run's Step 3.error-report.md. Previous results are
not preserved (the ledger, not old reports, is the historical record).entropy-scan's append-only quality log,
state/error-patrol-ledger.json is fully overwritten each run with current state.--dry-run mutates nothing. No report write, no ledger write — everything that would
be written is printed to stdout instead.--summary skips only the report file, not the ledger update — the ledger must stay
current so the next run's diff is correct regardless of which flag was used.