Build a performance bot with Sentry + Claude
Use Sentry span data to detect slow requests, send trace context to Claude for root cause analysis, and open GitHub pull requests with fixes — on a schedule, without anyone having to ask.
Before you start
Accounts & access
- Sentry account with tracing enabled
- Anthropic API key for Claude access
- GitHub repo + personal access token
Runtime
- Node.js 18+ or Python 3.10+
Knowledge
- Basic familiarity with Sentry tracing
- Understanding of REST APIs and GitHub's API
1 Connect Sentry MCP to Claude
Claude connects to Sentry via the Model Context Protocol (MCP) — no custom API wrapper needed. Once connected, Claude can query your spans, pull full trace details, and read issue context directly. In your Claude settings, open the Connectors panel and add Sentry. You will need your organization slug and a Sentry auth token with event:read and org:read scopes. The MCP connection gives Claude read-only access to your Sentry data.
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
body: JSON.stringify({
model: 'claude-sonnet-4-6',
mcp_servers: [{
type: 'url',
url: 'https://mcp.sentry.dev/mcp',
name: 'sentry'
}],
// ... rest of your request
})
});
2 Find slow spans in Sentry
Before Claude can suggest fixes, you need to know what is actually slow. Query Sentry's spans dataset for requests above a duration threshold, sorted by P95. This gives you a ranked list of your worst-performing endpoints. Grab the trace URL of the top offender — this is what you will pass to Claude.
Sentry performance monitoring// GET /api/0/organizations/{org}/events/
// ?field=span.op,span.description,p95,p99,count
// &query=span.duration:>2000
// &sort=-p95&dataset=spansIndexed&per_page=5
const slowest = data.data[0];
const traceUrl = `https://sentry.io/organizations/${ORG}/performance/trace/${slowest.trace}/`;
const endpoint = slowest['span.description'];
const p95ms = slowest.p95;
3 Send the trace to Claude for analysis
Call the Claude API, pass the trace URL, and ask Claude to diagnose the slowdown and produce concrete code fixes. The prompt structure matters — ask Claude to name the specific slow span, propose 1–3 changes, and return unified diffs so the output can be applied programmatically. For precise, line-number-accurate diffs, also pass the relevant source file contents in the prompt.
Claude tool useconst prompt = `
You are a performance engineer reviewing a slow request.
Sentry trace: ${traceUrl}
Endpoint: ${endpoint}
P95 latency: ${p95ms}ms | Threshold: ${THRESHOLD_MS}ms
Using the Sentry MCP tool, pull the full trace details. Then:
1. Name the specific span responsible for the slowdown
2. Propose 1–3 code changes that would fix it
3. Return each fix as a unified diff
Respond ONLY with valid JSON:
{
"root_cause": "string",
"changes": [{ "file": "string", "diff": "string", "summary": "string" }],
"risk_notes": "string"
}
`;
const res = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'claude-sonnet-4-6',
max_tokens: 4000,
mcp_servers: [{ type: 'url', url: 'https://mcp.sentry.dev/mcp', name: 'sentry' }],
messages: [{ role: 'user', content: prompt }]
})
});
const analysis = JSON.parse(res.content[0].text);
4 Open a pull request with the fix
Claude returns structured JSON with the root cause and diffs. Turn that output into a real GitHub PR that engineers can review and merge. Include the Sentry trace link, root cause summary, what changed and why, and any caveats Claude flagged. Before opening a PR, check if one already exists for the same trace ID to avoid duplicates stacking up in your queue.
GitHub Pulls APIconst { root_cause, changes, risk_notes } = analysis;
for (const change of changes) {
const file = await octokit.repos.getContent({ owner, repo, path: change.file });
const updated = applyPatch(atob(file.data.content), change.diff);
await octokit.git.createRef({ owner, repo,
ref: `refs/heads/perf/${traceId}`, sha: baseSha });
await octokit.repos.createOrUpdateFileContents({
owner, repo, path: change.file,
message: `perf: ${root_cause}`,
content: btoa(updated), branch: `perf/${traceId}`
});
await octokit.pulls.create({
owner, repo,
head: `perf/${traceId}`, base: 'main',
title: `[Perf] ${root_cause}`,
body: buildPRBody(analysis, traceUrl)
});
}
5 Schedule it
The last step is making this autonomous. GitHub Actions is the simplest choice — add a schedule trigger with a cron expression. Weekly is a good starting cadence. Daily runs tend to be noisy until you have tuned the threshold. Add a MAX_PRS_PER_RUN cap (start at 1–2) so the bot does not flood your PR queue on its first run.
# .github/workflows/perf-bot.yml
name: performance-bot
on:
schedule:
- cron: '0 9 * * 1' # Every Monday at 9am UTC
workflow_dispatch:
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm install
- run: node scripts/perf-bot.js
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
6 Verify with traces
After the first run, confirm the pipeline worked end-to-end. Check that the bot identified slow spans correctly, that Claude's analysis references real span names from the trace, and that the PR description includes the Sentry trace link. Open the trace in the Trace Explorer and compare the span names Claude mentions against the waterfall. Make sure your Sentry token has event:read scope — the API returns 200 with empty results if it is missing, not a 403.
7 Tune the threshold and extend
Once the pipeline is running, tune it to match your team's signal-to-noise tolerance. Start with a high threshold (3000ms) and lower it as you build confidence in PR quality. From here, add Slack notifications when a PR opens, filter by environment, or add a second Claude pass that estimates the expected latency improvement before opening a PR.
Performance monitoringconst CONFIG = {
thresholdMs: 3000, // Only look at requests slower than this
maxPrsPerRun: 2, // Cap PRs opened per scheduled run
environment: 'production', // Only analyze production traces
slackWebhook: process.env.SLACK_WEBHOOK,
dryRun: false, // Set true to log without touching GitHub
};
That's it.
Your performance bot is running.
Transaction regressions will now trigger an automated Claude analysis and post a summary to your PR — without you having to check.
- Connected Sentry MCP to Claude for read-only trace access
- Queried Sentry for slow spans and extracted trace context
- Sent traces to Claude for root cause analysis and structured fixes
- Opened GitHub PRs with Claude-generated diffs automatically
Pro tips
- 💡 Pass source file contents to Claude for exact line numbers rather than illustrative diffs.
- 💡 Add dryRun: true while testing — logs everything without touching GitHub.
- 💡 Start weekly; daily is noisy until you've tuned the threshold.
- 💡 You can also use Sentry Crons to monitor the bot itself, or Claude tasks as the scheduler without any external cron infrastructure.
Common pitfalls
- ⚠️ Using issue:read scope instead of event:read — empty results, no error.
- ⚠️ Skipping the dedup check — the same slow span reopens a PR every Monday until merged.
- ⚠️ Opening too many PRs at once erodes reviewer trust — cap at 1–2 per run while starting out.
- ⚠️ Without source file content in the prompt, Claude generates illustrative diffs rather than exact patches.
Frequently asked questions
What's next?
Fix it, don't observe it.
Get started with the only application monitoring platform that empowers developers to fix application problems without compromising on velocity.