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.

Features
SDKs
Category Workflow
Share
Time
~2 hours
Difficulty
Intermediate
Steps
7 steps

Before you start

Accounts & access
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.

Sentry integrations
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 use
const 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 API
const { 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 Actions workflows
# .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.

Sentry trace view

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 monitoring
const 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

No. The MCP connection is read-only. Claude can query spans, pull trace details, and read issue context — it cannot create, update, or delete anything in your Sentry account.
Without source file content in the prompt, Claude generates illustrative diffs based on span context rather than exact patches. Pass the actual file contents and the diffs will be precise enough to apply with standard patch tools.
A typical run queries 1–5 traces and makes 1–5 Claude API calls. At Sonnet pricing, a full weekly run analyzing the top 3 slow spans costs under $0.50 in API credits. GitHub Actions compute is free on most plans for this workload.
Yes. The Anthropic Python SDK supports the same mcp_servers parameter. Use the anthropic pip package and PyGithub for the PR step.

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.