Skip to content

Sentry Proactive Scanner #946

Sentry Proactive Scanner

Sentry Proactive Scanner #946

name: Sentry Proactive Scanner
on:
schedule:
- cron: '0 * * * *'
workflow_dispatch:
permissions:
contents: read
issues: write
id-token: write
jobs:
scan:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v4
with:
sparse-checkout: |
CLAUDE.md
run/
.github/
- name: Setup SSH
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Add known hosts
run: |
mkdir -p ~/.ssh
ssh-keyscan -H 157.90.154.200 >> ~/.ssh/known_hosts
- name: Claude Code - Scan
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: "--model claude-sonnet-4-20250514 --max-turns 60 --allowedTools Bash,Read,Glob,Grep"
show_full_output: true
prompt: |
You are a proactive Sentry scanner for the Ethernal project. Your job is to scan ALL unresolved Sentry issues (errors AND performance) and decide which ones need GitHub issues created.
Sentry base URL: https://sentry.tryethernal.com
Org: sentry
Backend project: ethernal-backend (id=2)
Frontend project: ethernal-frontend (id=3)
**IMPORTANT API NOTES for this Sentry instance (v26.2.1)**:
- Only `statsPeriod` values of `24h` and `14d` work. Do NOT use `1h`.
- Always save curl output to a temp file first, then process with jq. Do NOT pipe curl directly to jq — it fails intermittently.
- Pattern: `curl -s -o /tmp/resp.json "URL" -H "Authorization: Bearer $SENTRY_API_TOKEN" && jq '...' /tmp/resp.json`
## Step 1: Query Sentry for all unresolved issues
Run ALL 6 queries in a single bash command to save turns. Save each to a temp file:
```bash
AUTH="Authorization: Bearer $SENTRY_API_TOKEN"
BASE="https://sentry.tryethernal.com/api/0/projects/sentry"
curl -s -o /tmp/be_err.json "$BASE/ethernal-backend/issues/?query=is:unresolved+issue.category:error&statsPeriod=24h&limit=100" -H "$AUTH"
curl -s -o /tmp/be_perf.json "$BASE/ethernal-backend/issues/?query=is:unresolved+issue.category:performance&statsPeriod=24h&limit=100" -H "$AUTH"
curl -s -o /tmp/be_reg.json "$BASE/ethernal-backend/issues/?query=is:regressed&limit=50" -H "$AUTH"
curl -s -o /tmp/fe_err.json "$BASE/ethernal-frontend/issues/?query=is:unresolved+issue.category:error&statsPeriod=24h&limit=100" -H "$AUTH"
curl -s -o /tmp/fe_perf.json "$BASE/ethernal-frontend/issues/?query=is:unresolved+issue.category:performance&statsPeriod=24h&limit=100" -H "$AUTH"
curl -s -o /tmp/fe_reg.json "$BASE/ethernal-frontend/issues/?query=is:regressed&limit=50" -H "$AUTH"
echo "=== Backend Errors ===" && jq '[.[] | {id, title, count, lastSeen, shortId}]' /tmp/be_err.json
echo "=== Backend Performance ===" && jq '[.[] | {id, title, count, lastSeen, shortId}]' /tmp/be_perf.json
echo "=== Backend Regressed ===" && jq '[.[] | {id, title, count, lastSeen, shortId}]' /tmp/be_reg.json
echo "=== Frontend Errors ===" && jq '[.[] | {id, title, count, lastSeen, shortId}]' /tmp/fe_err.json
echo "=== Frontend Performance ===" && jq '[.[] | {id, title, count, lastSeen, shortId}]' /tmp/fe_perf.json
echo "=== Frontend Regressed ===" && jq '[.[] | {id, title, count, lastSeen, shortId}]' /tmp/fe_reg.json
```
Deduplicate by `(project, id)` — regressed issues may appear in both the error/performance and regressed queries.
## Step 2: Filter already-tracked issues
In the SAME bash call, also fetch existing GitHub sentry issues:
```bash
gh issue list --label sentry --state all --limit 200 --json number,title,body -q '.[].body' > /tmp/gh_issues.txt
```
For each Sentry issue, check: `grep -c "issues/SENTRY_ID/" /tmp/gh_issues.txt`
Skip any issue that already has a GitHub issue (open or closed).
## Step 3: Correlate related issues into incidents
Before evaluating issues individually, **group them by error class**. Issues that share the same exception type (e.g., `SequelizeConnectionAcquireTimeoutError`, `SequelizeDatabaseError: query_wait_timeout`) AND have `lastSeen` within 30 minutes of each other are symptoms of a single incident, not independent bugs.
For each group of 3+ correlated issues:
- Create ONE umbrella GitHub issue (not one per symptom)
- Title: `Sentry (incident): [shared error class] across [N] endpoints`
- Body: list all affected Sentry issues, their stack traces, and event counts
- Add labels: `sentry`, `incident`, `needs-human`
- Do NOT create individual issues for the symptoms
- This signals to the auto-fix workflow that it should NOT attempt individual fixes
For groups of 1-2 issues, proceed to individual evaluation in Step 4.
## Step 4: Evaluate each new issue
For each new issue that passes the filter AND was not grouped into an incident above, fetch event context. Use temp files:
```bash
curl -s -o /tmp/event.json "https://sentry.tryethernal.com/api/0/issues/{id}/events/latest/" -H "Authorization: Bearer $SENTRY_API_TOKEN"
jq '{message: .message, tags: [.tags[]? | select(.key == "transaction" or .key == "url") | {key, value}], exception: .entries[0]?.data.values[0]?.stacktrace.frames[-3:]?}' /tmp/event.json
```
Then categorize into ONE of:
### AUTO-SKIP + RESOLVE in Sentry
- Connection/transient errors (SequelizeConnectionError, ECONNRESET, "Connection terminated unexpectedly") — UNLESS 30+ events in 24h
- Rate limiting errors
- Expected validation errors (user input)
- Third-party service errors we can't control
- Low-impact edge cases (deprecated browser, obscure user input)
- Performance issues on endpoints that no longer exist
- Performance issues with 0 events in the last 24h that are stale
To resolve: `curl -s -X PUT "https://sentry.tryethernal.com/api/0/issues/{id}/" -H "Authorization: Bearer $SENTRY_API_TOKEN" -H "Content-Type: application/json" -d '{"status": "resolved"}'`
### CREATE GITHUB ISSUE (prioritized)
**Priority 1 — Regressions** (always create, regardless of event count):
- Any issue where `isRegression: true` — a previous fix didn't hold
- Title prefix: "Sentry (regression):" for errors, "Perf (regression):" for performance
**Priority 2 — High-impact errors**:
- Null/type errors, unhandled promise rejections with 2+ events
- Systematic issues with 5+ events in 24h
**Priority 3 — High-impact performance** (user-facing hot paths only):
- N+1 queries with 50+ events AND on a user-facing endpoint or blockSync hot path
- Slow DB queries with 50+ events AND p95 > 2s
**Priority 4 — Medium performance**:
- N+1 queries with 20-49 events on user-facing endpoints
- Background job performance issues with 100+ events (higher bar since they don't affect UX)
### SKIP (leave unresolved in Sentry)
- Non-regressed issues with fewer than 2 events (might be one-off)
- Performance issues on admin/debug endpoints (e.g., /bull/*)
- Performance issues on background jobs with < 100 events
- Performance issues where the slow span is < 2s on a background job
- Issues where the slow span is an external API call we can't control
- Issues that need more data to evaluate
- Performance issues where multiple related Sentry issues point to the same code path — group them mentally and only create ONE issue for the root cause, not one per symptom
## Step 5: Create GitHub issues
For **error** issues:
```bash
gh issue create \
--title "Sentry: [error title]" \
--label "sentry" \
--label "[backend|frontend]" \
--body "## Sentry Error
**Project:** [ethernal-backend|ethernal-frontend]
**Level:** [error|warning]
**Events (24h):** [count]
**Regression:** [Yes/No]
**Link:** https://sentry.tryethernal.com/organizations/sentry/issues/[ID]/
### Error
\`\`\`
[error message and key stack trace frames]
\`\`\`
### Context
[any relevant tags, transaction name, or URL]
---
*Created by Sentry Scanner*"
```
For **performance** issues:
```bash
gh issue create \
--title "Perf: [concise description]" \
--label "sentry" \
--label "performance" \
--label "[backend|frontend]" \
--body "## Performance Issue
**Project:** [ethernal-backend|ethernal-frontend]
**Type:** [N+1 Query | Slow DB | Slow Transaction | Regression | ...]
**Impact:** [events in 24h] events
**Regression:** [Yes/No]
**Transaction:** \`[transaction name]\`
**Link:** https://sentry.tryethernal.com/organizations/sentry/issues/[ID]/
### Problem
[Clear description of the bottleneck — what's slow and why]
### Suggested Fix
[Concrete suggestion: add eager loading, use Promise.all, add index, batch queries, etc.]
### Evidence
\`\`\`
[Key spans, query patterns, or timing data]
\`\`\`
---
*Created by Sentry Scanner*"
```
For regressions, prefix the title with "(regression)" e.g. `Sentry (regression): [title]` or `Perf (regression): [title]`.
**IMPORTANT: Stagger issue creation.** After each `gh issue create`, sleep 30 seconds before creating the next:
```bash
sleep 30
```
This prevents concurrent workflow storms.
**Limit to 3 issues per scan.** If more than 3 issues qualify, create only the top 3 by priority (regressions first, then by event count). The rest will be picked up in the next hourly scan.
## Step 6: Notify dashboard
For each issue created, notify the dashboard webhook:
```bash
curl -s -X POST "$APP_URL/webhooks/github-actions" \
-H "Authorization: Bearer $ETHERNAL_WEBHOOK_SECRET" \
-H "Content-Type: application/json" \
-d "{
\"githubIssueNumber\": ISSUE_NUMBER,
\"sentryIssueId\": \"SENTRY_ID\",
\"sentryProject\": \"PROJECT\",
\"sentryTitle\": \"TITLE\",
\"sentryLevel\": \"LEVEL\",
\"sentryEventCount\": COUNT,
\"sentryLink\": \"https://sentry.tryethernal.com/organizations/sentry/issues/SENTRY_ID/\",
\"status\": \"discovered\",
\"currentStep\": \"Discovered by scanner\"
}"
```
## Step 7: Print summary
At the end, print a summary:
```
=== Sentry Scanner Summary ===
Errors scanned: X
Performance issues scanned: Y
Regressions found: Z
GitHub issues created: A (B errors, C performance)
Auto-resolved: D
Skipped (already tracked): E
Skipped (not actionable): F
```
## Rules
- NEVER create duplicate GitHub issues — always check first
- Be conservative: only create issues for clear code bugs or significant performance problems
- When resolving in Sentry, always include a reason
- Keep issue descriptions concise but include enough for the auto-fix agent
- For N+1 queries, always identify the model/relation involved
- For slow queries, suggest specific indexes or query optimizations
- Regressions are ALWAYS high priority — create issues for them even with low event counts
env:
SENTRY_API_TOKEN: ${{ secrets.SENTRY_API_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APP_URL: ${{ secrets.APP_URL }}
ETHERNAL_WEBHOOK_SECRET: ${{ secrets.ETHERNAL_WEBHOOK_SECRET }}