← 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/awaiting-seer.ts
56 lines · 1.9 KB · TypeScriptimport { seerGraceMs, seerPrGraceMs } from './shared.ts'
import { listStorageEntries, recordFromListEntry } from './list-storage.ts'
export function awaitingSeerGraceMs(status) {
if (status === 'awaiting-seer') return seerGraceMs
if (status === 'awaiting-seer-pr') return seerPrGraceMs
return null
}
export function awaitingSeerSinceMs(record) {
return (
Date.parse(
record?.seerPrAwaitedAt ??
record?.seerRequestedAt ??
record?.seenAt ??
'',
) || 0
)
}
/**
* Whether a stored issue record is stuck awaiting Seer past its grace
* window. Automation-owned Seer runs produce no later webhook when Sentry
* declines an issue, so this is the routing signal for from-scratch rescue.
*/
export function isAwaitingSeerStale(record, now = Date.now()) {
const graceMs = awaitingSeerGraceMs(record?.status)
if (graceMs == null) return false
if (!record?.issueId || !record.projectSlug) return false
return now - awaitingSeerSinceMs(record) > graceMs
}
/**
* Find issue records stuck awaiting Seer past their grace window, oldest
* first. Automation-owned Seer runs produce no later webhook when Sentry
* declines an issue, so rescue cannot wait on that issue's own delivery.
* Drivers: the inbound-issue sweeper (opportunistic), `./sweep-stale-awaiting`
* every 15m (quiet-period), and daily `./reconcile` (completeness).
*
* Uses paginated packageStorage.list — a single page silently misses newer
* `issue:` keys once the store grows past the default page size.
*/
export async function findStaleAwaitingIssues(
storage,
limit = 2,
now = Date.now(),
) {
const rawEntries = await listStorageEntries(storage, { prefix: 'issue:' })
const stale = []
for (const entry of rawEntries) {
const record = await recordFromListEntry(storage, entry)
if (isAwaitingSeerStale(record, now)) stale.push(record)
}
stale.sort((left, right) => awaitingSeerSinceMs(left) - awaitingSeerSinceMs(right))
return stale.slice(0, Math.max(0, limit))
}