← Public packages
@kentcdodds/kody-issue-triage
Loop-safe triage for Kody run errors and fleet package-runtime error-rate elevations. Wakes Cole (Grok Bot) to decide; Cursor agents only when Cole escalates.
src/on-run-error.ts
130 lines · 3.4 KB · TypeScriptimport { kody } from 'kody:runtime'
import {
AGENT_EXECUTE_NOISE_NOTE,
hourBucket,
PLATFORM_WEATHER_NOTE,
shouldIgnorePlatformWeather,
skipReason,
} from './shared.ts'
import {
incrementCounter,
initSchema,
updateFingerprint,
upsertFingerprintFromRun,
} from './storage.ts'
import { scheduleEventSweep } from './schedule-event-sweep.ts'
export type RunErrorRecordedEvent = {
event?: string
run?: {
id?: string
surface?: string
name?: string | null
package_id?: string | null
kody_id?: string | null
job_id?: string | null
error_name?: string | null
error_message?: string | null
started_at?: string | null
/** Omitted on production `run.error.recorded`; present in run_list/run_get. */
metadata?: Record<string, unknown> | null
}
activity_url?: string
synthetic?: boolean
}
async function softTriageRun(
runId: string | null | undefined,
triage: 'ignored' | 'resolved',
note: string,
) {
if (!runId) return
try {
await kody.runUpdate({
run_id: runId,
triage,
note,
})
} catch {
// Best-effort Activity close; fingerprint status is the standing record.
}
}
/**
* Cheap `run.error.recorded` handler. Fingerprint + counter only, except a
* cheap Activity `runUpdate` when the family is already standing noise
* (platform isolate-reset weather or ignored / resolved_noise) or the run is
* agent MCP execute-origin (caller already has the error). Never posts Discord or fetches run detail inline. When a fingerprint is
* newly actionable it schedules a guarded `./sweep` workflow so Patch can wake
* without waiting for the hourly job.
*/
export default async function onRunError(event: RunErrorRecordedEvent = {}) {
const run = event.run
if (!run?.id && !run?.error_message && !run?.surface) {
return { ok: true, skipped: 'empty-event' }
}
const skip = skipReason(run)
if (skip === 'agent-execute') {
await softTriageRun(run.id, 'ignored', AGENT_EXECUTE_NOISE_NOTE)
return { ok: true, skipped: skip, runId: run.id || null, ignoredExecute: true }
}
if (skip) return { ok: true, skipped: skip, runId: run.id || null }
await initSchema()
const bucket = hourBucket()
const upserted = await upsertFingerprintFromRun({
...run,
activity_url: event.activity_url || null,
metadata: run.metadata ?? null,
})
const eventsThisHour = await incrementCounter(`events:${bucket}`)
if (upserted.created) {
await incrementCounter(`fingerprints:${bucket}`)
}
const weather = shouldIgnorePlatformWeather({
...run,
metadata: run.metadata ?? null,
})
const standingNoise =
weather ||
upserted.nextStatus === 'ignored' ||
upserted.nextStatus === 'resolved_noise'
if (standingNoise) {
if (weather) {
await updateFingerprint(upserted.fingerprint, {
status: 'resolved_noise',
outcome: 'resolved_noise',
classification: 'noise',
completed_at: new Date().toISOString(),
summary: PLATFORM_WEATHER_NOTE,
})
}
await softTriageRun(
run.id,
'resolved',
weather
? PLATFORM_WEATHER_NOTE
: 'Standing ignored/resolved_noise fingerprint; do not refill Activity.',
)
}
const queued = !standingNoise
const sweep = await scheduleEventSweep({
fingerprint: upserted.fingerprint,
queued,
})
return {
ok: true,
queued,
fingerprint: upserted.fingerprint,
created: upserted.created,
count: upserted.count,
eventsThisHour,
resolvedNoise: standingNoise || undefined,
synthetic: event.synthetic === true,
eventSweep: sweep,
}
}