You're an expert debugging engineer running in a recurring loop. Each iteration should make incremental progress — discover new issues, fix what you can, monitor existing PRs, and clean up after yourself.
This prompt runs via /loop. Each iteration MUST be idempotent:
- Read
.agents/state/sentry-patrol.jsonat the start of every iteration to understand what has already been discovered, fixed, or skipped. - Write updated state back at the end of every iteration.
- Never create a duplicate PR for an issue that already has one (check state file AND
gh pr list --author @me --state open). - If there is nothing new to do AND no
discoveredissues remain to investigate AND no PR feedback to address, say so and exit early. Don't re-investigate known issues. - Every iteration must make forward progress. If there are
discoveredissues waiting, investigate them. If all issues are handled, scan for new ones. Only idle when all issues are investigated/skipped/fixed AND all PRs are green with no feedback to address.
{
"lastRun": "2026-03-23T00:00:00Z",
"issues": {
"<sentry-issue-id>": {
"title": "...",
"project": "app|notes-service|...",
"status": "discovered|investigating|fixed|skipped|pr-open|pr-review-pending|pr-merged",
"prNumber": null,
"prBranch": null,
"skipReason": null,
"lastSeen": "2026-03-23T00:00:00Z",
"botReviewStatus": null
}
}
}If the state file doesn't exist, create it. If it does, load it and continue from where the last iteration left off.
Before doing anything else, aggressively clean up worktrees left behind by previous runs:
- Run
git worktree listand identify ALL.claude/worktrees/*entries. - For each worktree, check whether it is still actively needed:
- If the associated branch has an open PR that is still being worked on this iteration, keep it.
- Otherwise, remove it with
git worktree remove --force <path>and thengit branch -D <branch>if the branch was fully merged or the PR was already merged/closed.
- Run
git worktree pruneto clean up any stale worktree references pointing to deleted directories. - If any
.claude/worktrees/directories remain on disk but aren't tracked bygit worktree list, remove them withrm -rf. - Check state file for issues with
status: "pr-open"or"pr-review-pending"— rungh pr view <prNumber> --json state,mergedAt,statusCheckRollupto update their status. If merged, markpr-merged. If CI is failing or bot reviews are pending, note it for Phase 4.
Query the Sentry API (https://sentry.io/api/0/) using SENTRY_AUTH_TOKEN env var as Bearer token. Org slug: dovetail.
Projects to scan: app, notes-service, website, zapier, app-lambda, tooling-lambda, redaction-service.
- Sort by frequency, paginate through all pages. Use
statsPeriod=14d(only24hand14dare valid). - For each issue: if its Sentry issue ID is already in the state file, skip it.
- Add newly discovered issues to the state file with
status: "discovered". - Also check
gh pr list --author @me --state open --label "AI assisted"and cross-reference — if a PR already targets a Sentry issue, mark it accordingly. - Prioritize DB performance issues: Also search Sentry with queries like
deadlock,connection,pool,timeoutto surface database-related errors specifically.
Don't just skim the top issues by count. Many high-value fixes hide in less obvious places:
- Vague titles deserve investigation, not dismissal. An issue titled "captureException" with 67K events turned out to be
react-beautiful-dndthrowing plain objects that Sentry's instrumentation captured — a one-linebeforeSendfilter eliminated all of them. Investigate the event payload before skipping. - Look for patterns across issues. If one AI output parsing error was fixed with
.nullish()in a Zod schema, scan for the same pattern in other AI service schemas (MagicTag, InferredSuggestion, etc.). - Check if a fix in one code path was missed in a parallel path. A
bulkCopyNotesnull safety fix was applied toNoteServicebut the same bug existed inProjectService.copyProject. Search for sibling code paths. - Low-frequency issues can still be high-impact. A DB constraint violation with 13 events might affect paying customers silently. A 1-event URL construction error reveals missing input validation on auth endpoints.
- Correlate Sentry errors with Datadog metrics. Rising
waiting_queries+ statement timeout Sentry errors = lock contention. Risingdatabase_connections+ pool errors = connection exhaustion. The fix is often in code (transaction splitting, connection release), not infrastructure. - Scan with
per_page=100and look beyond page 1. The defaultper_page=25misses mid-tier issues that are still fixable.
Query Datadog for database performance metrics to identify systemic issues and correlate with Sentry errors. Use curl -s -G with --data-urlencode for all Datadog timeseries queries (required for {*} syntax):
curl -s -G "https://api.us3.datadoghq.com/api/v1/query" \
--data-urlencode "from=$(date -v-7d +%s)" \
--data-urlencode "to=$(date +%s)" \
--data-urlencode "query=max:postgresql.queries.duration.max{*} by {db}" \
-H "DD-API-KEY: ${DD_API_KEY}" \
-H "DD-APPLICATION-KEY: ${DD_APP_KEY}"Key metrics to check:
max:postgresql.queries.duration.max{*} by {db}— slow queriesmax:postgresql.locks{*}— lock contentionmax:postgresql.waiting_queries{*}— blocked queriesmax:aws.rds.database_connections{*}— connection pressuremax:aws.rds.read_latency{*},max:aws.rds.write_latency{*}— I/O pressure
Cross-reference elevated metrics with Sentry errors to identify root causes (e.g. high waiting_queries + deadlock Sentry errors = lock contention issue).
This phase runs every iteration, not just once. If there are issues with status: "discovered", they must be investigated — do not skip this phase just because agents ran in a previous iteration. If discovery found no new issues but there are still discovered issues in the state file, investigate those.
Pick up to 4 issues with status: "discovered", prioritized by frequency. When all high-frequency issues are handled, move to mid-tier and low-frequency issues — don't stop just because the remaining issues have fewer events. A 13-event DB constraint violation or a 1-event URL error can still reveal important bugs worth fixing.
For each issue, launch a worktree agent in parallel (isolation: "worktree") with this task:
Investigate Sentry issue {id} ("{title}") in project {project}.
- Fetch the latest event stacktrace via
/issues/{id}/events/latest/from the Sentry API.- Use Datadog logs API (
https://api.us3.datadoghq.com/api/v2/logs/events/search) withDD-API-KEYandDD-APPLICATION-KEY(env var:DD_APP_KEY) headers to correlate error patterns.- If the error involves database timeouts, connection failures, deadlocks, or slow queries, query Datadog for DB-level metrics using the timeseries API (
https://api.us3.datadoghq.com/api/v1/query). Useful metrics:
postgresql.queries.duration.max/postgresql.transactions.duration.max— slow queries/transactionspostgresql.locks,postgresql.deadlocks,postgresql.deadlocks.count— lock contentionpostgresql.waiting_queries,postgresql.active_waiting_queries— blocked queriespostgresql.activity.wait_event,postgresql.activity.blocked_connections— what's blockingpostgresql.connections,postgresql.percent_usage_connections— pool exhaustionaws.rds.read_latency,aws.rds.write_latency— disk I/O pressureaws.rds.blocked_transactions_count,aws.rds.idle_in_transaction_sessions_count— idle-in-transaction leaksaws.rds.database_connections,aws.rds.cpuutilization— instance-level health- Trace through the source code to find the exact code path.
- If you can fix it with high confidence and low blast radius:
- Always branch from
origin/master: rungit fetch origin master && git checkout -b dougrathbone/fix-{short-description} origin/master. Never branch off another feature branch — this is the #1 cause of PRs containing unrelated commits. The only exception is stacked PRs for a single initiative that require staged merging (e.g. an NX upgrade split into sequential steps), where each PR explicitly targets the previous branch as its base.- Implement the fix
- Add unit tests for any new behavior. Tests must cover the core invariant of the fix — not just that the code runs, but that the specific behaviour change works correctly. For example, if a fix splits a single transaction into multiple transactions, test that the split happens as expected and that partial failure is handled safely. If a fix adds a null guard, test both the null and non-null paths. Leaving a fix untested is not acceptable — reviewers will flag it.
- Run
yarn nx run <workspace>:test.jest -- --testPathPattern="<relevant test>"to verify tests pass- Run
yarn nx run <workspace>:test.tscto type-check- Run
yarn nx run <workspace>:buildto verify compilation- Run
yarn fix.oxlintandyarn nx run <workspace>:fix.format- Commit and push
- Verify the branch is clean before creating the PR:
- Run
git log --oneline origin/master..HEADand confirm every commit is related to this fix. If unrelated commits are present (e.g. from branching off another feature branch), rebase: create a fresh branch fromorigin/master, cherry-pick only the relevant commits, and force-push with--force-with-lease.- Run
git diff --name-only origin/master..HEADand confirm every changed file is relevant to the fix. Flag any unexpected files.- Create a draft PR with
gh pr create --draft --title "fix: {description}" --label "AI assisted" --label "bugfix"using the PR template at.github/pull_request_template.md. Do NOT add AI attribution in the PR body.- Include in the PR body: Sentry link, stacktrace analysis, code tracing steps, blast radius assessment, rollback plan.
- For DB-related issues, also include in the PR body: which Datadog metrics were queried, the timeframe examined, metric values observed (e.g. p99 query duration, lock counts, connection pool utilization), and how the fix addresses the specific DB performance problem.
- Return the PR number and branch name.
- If you cannot fix it confidently, return a summary of your investigation and recommend
skippedwith a reason.Known fix categories for reference:
- Apollo subscription null safety:
updateQueryhandlers accessing.nodeswithout?.- Invariant replacements:
assert.invariantthat should gracefully handle edge cases- Codec safety:
decodeOrThrowin non-critical paths replaced withdecodeOrNull- Shutdown noise: Pool lifecycle errors during graceful shutdown
- Webhook resilience: Strict codec unions rejecting new external data types
- Sentry noise reduction: Transient errors downgraded from
reportErrortowarn- Database timeouts: Missing statement timeouts, unbounded queries, missing indexes, idle-in-transaction from unclosed connections
- Connection pool exhaustion: Leaked connections from missing
finallyblocks or un-awaited DataApi releases- Deadlock retry: PostGraphile GraphQL mutations that deadlock on
FOR UPDATE— catch withisPgError+isRetryablePgTransactionErrorand return a client-retryable error response. Note:BetterPgPool.transaction()already retries deadlocks for background jobs; GraphQL mutations need handling at the resolver level.- Transaction safety: Use existing
dbTransactionSafetyContextandisRetryablePgTransactionErrorfrom@dvtl/postgres— never write custom deadlock detection
After all parallel agents complete, update the state file with results (PR numbers, skip reasons, etc.). Mark new PRs as "pr-review-pending" (not "pr-open") — they are created as drafts and are not done until CI passes, branch content is verified, bot reviews are addressed, and the PR is published in Phase 4.
A PR is NOT done just because CI is green. It must also pass content verification, bot reviews, and be promoted from draft to ready. For every issue with status: "pr-open" or "pr-review-pending":
- Run
gh pr checks <prNumber>to identify failures. - For simple failures (format, lint, type errors), launch a worktree agent to fix and push.
- For complex failures, update the state file with the failure details for human review.
Before promoting a draft PR, verify the branch only contains relevant changes:
- Run
gh pr view <prNumber> --json commits --jq '.commits[].messageHeadline'and confirm every commit message relates to the fix described in the PR title. - Run
gh pr diff <prNumber> --name-onlyand confirm every changed file is relevant. - If unrelated commits or files are found, launch a worktree agent to clean the branch: check out the branch, create a fresh branch from
origin/master, cherry-pick only the relevant commits, and force-push with--force-with-lease. The PR stays in"pr-review-pending"until the next iteration re-verifies.
- Run
gh pr view <prNumber> --json reviews,reviewRequests,commentsto check review status. - Check for pending review requests — if review bots (e.g.
bugbot,coderabbitai,github-actions[bot], or any bot reviewer) have not yet posted their review, keep the status as"pr-review-pending"and move on. Do NOT mark the PR as done. - Also check
gh pr view <prNumber> --json commentsfor bot comments that contain actionable feedback (look for comments from bot accounts that flag issues, suggest changes, or request fixes).
- If bot reviewers have posted reviews with
CHANGES_REQUESTEDor left comments flagging bugs/issues:- Launch a worktree agent to check out the PR branch and address each piece of bot feedback.
- Fix the issues, commit, and push to the same PR branch.
- After pushing fixes, the PR stays in
"pr-review-pending"— the bots need to re-review the new changes. It will be re-checked in the next iteration.
- If bot feedback is too complex to address automatically, update the state file with details for human review and mark accordingly.
Only publish a draft PR (transitioning from "pr-review-pending" to "pr-open") when ALL of the following are true:
- CI is passing
- Branch content is verified (no unrelated commits or files)
- All bot reviewers have posted their reviews
- No bot reviews have
CHANGES_REQUESTEDstatus or unresolved bot-flagged issues
To publish: gh pr ready <prNumber>
This ensures human reviewers only see PRs that are clean, passing, and bot-approved.
- Run
git worktree listand remove ALL.claude/worktrees/*entries that are no longer needed. A worktree is no longer needed if:- Its PR has been pushed and is not being actively fixed this iteration.
- Its PR has been merged or closed.
- Its associated branch has no corresponding open PR.
Remove with
git worktree remove --force <path>, thengit worktree prune.
- Verify no orphaned directories remain under
.claude/worktrees/— if they do, remove them withrm -rf. - Write the updated state file.
- Print a summary: new issues found, fixes submitted, PRs waiting for bot review, PRs ready for human review, PRs passing/failing, issues skipped, worktrees cleaned up.
- TypeScript monorepo with NX build system, Yarn v4.6.0
- Frontend: React, Apollo Client, ProseMirror editor
- Backend: PostGraphile GraphQL, PostgreSQL, PgBoss job scheduler, Redis
- CDC (Change Data Capture) pipeline processes database changes
processLifecycle.isShuttingDownflag for shutdown-aware code@dvtl/toolboxcodec utilities:decodeOrThrow,decodeOrNull,ErrorWithMeta- io-ts codecs with fp-ts
Either/isRightfor validation - Use
/babysit-prskill for monitoring CI on individual PRs - Datadog auth:
DD_API_KEYforDD-API-KEYheader,DD_APP_KEYforDD-APPLICATION-KEYheader - Datadog site:
us3.datadoghq.com - Buildkite auth:
BUILDKITE_API_TOKENenv var, used asAuthorization: Bearerheader. API base:https://api.buildkite.com/v2/organizations/dovetail/pipelines/platform-ci/builds/{buildNumber}. Use this to read build logs and identify CI failures — get failed jobs via.jobs[] | select(.state == "failed"), then fetch logs at/jobs/{jobId}/log.