summaryrefslogtreecommitdiff
path: root/.claude/workflows/pr-babysit.js
diff options
context:
space:
mode:
authorHiFiPHile <[email protected]>2026-09-01 04:54:08 +0200
committerHiFiPHile <[email protected]>2026-09-01 04:54:08 +0200
commite97cf0296dc1bab7c6d2020925245cd0bd42c475 (patch)
tree5faeaccfdcb168d1cf055fc404732eaa82071b4e /.claude/workflows/pr-babysit.js
parenta0e4f90765738a78d4a49cdc3b0725e04cd498fb (diff)
parent84dcec43c709643f8a1b1ebbab6d08fbbc605a6d (diff)
Merge remote-tracking branch 'origin/master' into agent/fix-dwc2-host-fifo-allocationagent/fix-dwc2-host-fifo-allocation
Diffstat (limited to '.claude/workflows/pr-babysit.js')
-rw-r--r--.claude/workflows/pr-babysit.js35
1 files changed, 34 insertions, 1 deletions
diff --git a/.claude/workflows/pr-babysit.js b/.claude/workflows/pr-babysit.js
index 2731ed254..8438d4d04 100644
--- a/.claude/workflows/pr-babysit.js
+++ b/.claude/workflows/pr-babysit.js
@@ -129,6 +129,10 @@ const postReplyRecipe = (noun) =>
const history = []
const repliedIds = new Set() // issue comments can't be thread-resolved, so they re-harvest every cycle — never reply twice
+// Backoff between cycles that have nothing to do but wait. Degrades to a no-op
+// rather than throwing if the workflow host has no timer.
+const nap = (ms) => new Promise(res => { if (typeof setTimeout === 'function') setTimeout(res, ms); else res() })
+
// Canonicalize a repo-relative path for set/collision comparison: resolve ./..
// segments, unify separators; '' for anything that escapes the repo or uses
// characters no repo path does (also makes the path shell-safe to interpolate).
@@ -200,11 +204,28 @@ const fixAndVerify = async (workIn) => {
}
work.push(g)
}
+ // HIL rig rosters (test/hil/*.json) describe physical hardware the user owns:
+ // never edit them autonomously — skipping/reshaping tests there papers over a
+ // failing fixture. A failure that needs hardware swapped or re-cabled stays RED
+ // for the user; roster edits happen only with the user's explicit approval.
+ const withheld = []
+ for (const w of work) {
+ for (const f of [...w.files]) if (/^test\/hil\/[^/]+\.json$/.test(f)) {
+ w.files.delete(f)
+ log(`fix for ${w.key}: ${f} is a HIL rig config — edits need user approval, dropped from scope`)
+ }
+ if (w.files.size === 0) {
+ withheld.push(w)
+ log(`fix for ${w.key}: only a HIL rig config edit would address it — leaving red for the user`)
+ }
+ }
+ for (const w of withheld) work.splice(work.indexOf(w), 1)
const scopeOf = (w) => [...w.files].join(', ')
const fixes = await pipeline(
work,
w => agent(
`Fix the following issues on the PR branch. ${IN_CHECKOUT}\n` +
+ 'Constraint: never modify test/hil/*.json (HIL rig hardware config) — a failure that needs hardware swapped/changed stays red for the user.\n' +
`Scope: ${scopeOf(w)}\nIssues:\n- ${w.notes.join('\n- ')}`,
{ label: `fix:${w.key}`, phase: 'Fix', agentType: 'code-writer', schema: DEV },
),
@@ -218,7 +239,7 @@ const fixAndVerify = async (workIn) => {
if (alive.length < work.length) log(`${work.length - alive.length} fix group(s) lost to dead workers`)
const unverified = alive.filter(f => f.addresses !== true)
for (const f of unverified) log(`fix for ${f.item}: failed verification — ${f.checkReason}`)
- return { ok: unscoped.length === 0 && alive.length === work.length && unverified.length === 0, fixes: alive }
+ return { ok: unscoped.length === 0 && withheld.length === 0 && alive.length === work.length && unverified.length === 0, fixes: alive }
}
// Verification gates every push: never push unverified or partial edits.
@@ -370,6 +391,18 @@ for (let cycle = 1; cycle <= maxCycles; cycle++) {
log(`cycle ${cycle}: CI still settling (${c.infraRerun.length} infra re-run(s)) — re-arming`)
continue
}
+ if (!r.done) {
+ // A bot has not reported for this head SHA yet. With CI already green there is
+ // nothing else to wait on, so back off before re-arming or the cycle budget
+ // burns on back-to-back re-harvests of the same unchanged PR.
+ if (cycle < maxCycles) {
+ log(`cycle ${cycle}: auto-review still pending — re-arming after a wait`)
+ await nap(60000 * cycle) // no wait on the last cycle: nothing would re-check after it
+ } else {
+ log(`cycle ${cycle}: auto-review still pending — cycle budget exhausted`)
+ }
+ continue
+ }
log(`cycle ${cycle}: nothing actionable`)
return { pass: false, cycles: cycle, history, reason: 'unactionable' }
}