From a50e9586f2086b479431bb00200ab9aeb482b354 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 27 Apr 2026 08:15:06 -0500 Subject: [PATCH] auditor: cap auto-resets on new head SHA (was per-PR-forever, now per-push) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Operator feedback: manual jq-edit-state.json + restart isn't sustainable. Each push should naturally get a fresh budget; old counter discarded the moment the SHA moves. Cap intent shifts from "PR exhaustion" to "per-push attempt limit" — bounded recovery from transient upstream errors, not a forever limit. Mechanism: - The dedup branch above (`last === pr.head_sha → continue`) unchanged. - New branch: when `last` exists AND we have a non-zero count, AND we've fallen through to here (which means SHA != last, i.e. a new push), drop the counter to 0 BEFORE the cap check. - Cap check fires only on same-SHA retries (transient errors that consumed multiple attempts). Net behavior: - push code → 3 audits run → cap → quiet → push more code → cap auto-resets → 3 more audits → cap → quiet - No manual jq ever needed in steady state. - Operator clears state.audit_count_per_pr. = 0 only if a single SHA somehow needs MORE than the cap. Pre-existing manual reset still works (state edit + daemon restart for the change to take effect). Documented in the new log line that fires on the rare same-SHA-burned-cap case. Verified compile (bun build auditor/index.ts → green). Daemon restart needed to activate; current cycle 4616's `[1/3]` audit on 6ed48c1 finishes first, then restart. Co-Authored-By: Claude Opus 4.7 (1M context) --- auditor/index.ts | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/auditor/index.ts b/auditor/index.ts index 05a5972..c3bd9c0 100644 --- a/auditor/index.ts +++ b/auditor/index.ts @@ -115,13 +115,28 @@ async function runCycle(state: State): Promise { console.log(`[auditor] skip PR #${pr.number} (SHA ${pr.head_sha.slice(0, 8)} already audited)`); continue; } - // Per-PR audit cap — once a PR has been audited MAX_AUDITS_PER_PR - // times, halt further audits until the operator manually clears - // audit_count_per_pr[] in state.json. Prevents runaway burn - // when each fix surfaces new findings. + // Per-head-SHA audit cap. Each new push gets MAX_AUDITS_PER_PR + // fresh attempts; the counter auto-resets when the head SHA + // changes. Operator only intervenes manually if a single SHA + // somehow needs MORE than the cap (rare — usually transient + // upstream errors clear themselves inside 3 attempts). + // + // Reset rule: if `last` exists (we've seen this PR before) AND + // pr.head_sha != last, that's a new push. Drop the counter. + // The dedup branch above already handles same-SHA → skip, so + // we only land here when the SHA actually moved. + if (last !== undefined && (state.audit_count_per_pr[prKey] ?? 0) > 0) { + const prior_count = state.audit_count_per_pr[prKey]; + console.log(`[auditor] PR #${pr.number} new head ${pr.head_sha.slice(0, 8)} (prior ${last.slice(0, 8)}, was ${prior_count}/${MAX_AUDITS_PER_PR}) — resetting cap counter`); + state.audit_count_per_pr[prKey] = 0; + } const auditedSoFar = state.audit_count_per_pr[prKey] ?? 0; if (MAX_AUDITS_PER_PR > 0 && auditedSoFar >= MAX_AUDITS_PER_PR) { - console.log(`[auditor] skip PR #${pr.number} (capped at ${auditedSoFar}/${MAX_AUDITS_PER_PR} audits — clear state.json audit_count_per_pr.${prKey} to resume)`); + // This branch only fires now if the SAME head SHA somehow + // burned MAX audits (transient upstream errors retried that + // many times). Operator can clear state.audit_count_per_pr. + // = 0 to force one more attempt; otherwise wait for next push. + console.log(`[auditor] skip PR #${pr.number} (same head ${pr.head_sha.slice(0, 8)} burned ${auditedSoFar}/${MAX_AUDITS_PER_PR} — push new code or clear state.json audit_count_per_pr.${prKey})`); state.cycles_skipped_capped += 1; continue; }