Skip to content
← Public packages

@kentcdodds/sentry-triage

Sentry triage wakes Cole (Grok Bot) per issue: one active wake per repo (lease + queue), loop-safe Discord. Cole may spawn Cursor for isolated repo work.

src/prefetch-sentry-context.ts

76 lines · 2.3 KB · TypeScript
import getLatestIssueEvent from 'kody:@kentcdodds/sentry/get-latest-issue-event'
import searchIssues from 'kody:@kentcdodds/sentry/search-issues'
import { truncate } from './shared.ts'
import { readTriageSettings } from './settings.ts'

/**
 * Prefetch Sentry context once so every agent does not spend its opening
 * round trips on the same lookups (observed: even fast agents made ~24 MCP
 * calls, mostly Sentry fetches). Failures degrade to 'unavailable' — the
 * prompt tells agents how to fetch anything missing themselves. API access
 * goes through @kentcdodds/sentry so auth, host approval, and response
 * parsing live in one place.
 */
export async function prefetchSentryContext(issueId, project) {
	const context = { eventSummary: null, siblings: null, issueStats: null }
	try {
		const event = await getLatestIssueEvent({ issueId })
		const exception = (event.entries ?? []).find(
			(entry) => entry.type === 'exception',
		)
		const frames = (exception?.data?.values?.[0]?.stacktrace?.frames ?? [])
			.filter((frame) => frame.inApp)
			.slice(-6)
			.map(
				(frame) =>
					`${frame.filename ?? '?'} in ${frame.function ?? '?'}:${frame.lineNo ?? '?'}`,
			)
		context.eventSummary = {
			level: event.level ?? null,
			environment:
				(event.tags ?? []).find((t) => t.key === 'environment')?.value ??
				null,
			tags: Object.fromEntries(
				(event.tags ?? [])
					.filter((t) =>
						[
							'url',
							'transaction',
							'mcp.tool',
							'mcp.capability',
							'mcp.sandbox_error',
							'runtime',
							'mechanism',
						].includes(t.key),
					)
					.map((t) => [t.key, t.value]),
			),
			topFrames: frames,
			message: truncate(event.message ?? '', 300),
		}
	} catch {
		// Prompt instructs agents to fetch this themselves when missing.
	}
	try {
		const issues = await searchIssues({
			orgSlug: (await readTriageSettings()).sentryOrgSlug,
			project: project.projectId,
			query: 'is:unresolved',
			statsPeriod: '14d',
			limit: 12,
		})
		context.siblings = (Array.isArray(issues) ? issues : [])
			.filter((sibling) => String(sibling.id) !== String(issueId))
			.slice(0, 10)
			.map((sibling) => ({
				id: sibling.id,
				shortId: sibling.shortId,
				count: sibling.count,
				culprit: truncate(sibling.culprit ?? '', 80),
				title: truncate(sibling.title ?? '', 90),
			}))
	} catch {
		// Same degradation as above.
	}
	return context
}