From 50b888a7514cb15e28b7b8cf9d41c7bc98121fe5 Mon Sep 17 00:00:00 2001 From: m3taversal Date: Sun, 10 May 2026 19:00:48 +0100 Subject: [PATCH] fix(reaper): extend allowlist to */research-2* daily-cron sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply Step 1 of stuck-PR triage. The May 7 reaper allowlist (extract/, reweave/, fix/) deliberately excluded all agent-prefix branches per Ganymede's review nit #3 — the rationale being that agent branches are WIP feature work owned by the agent and shouldn't be auto-closed. That decision was correct for theseus/feature-foo style branches. It's wrong for {agent}/research-{YYYY-MM-DD} branches: those are daily cron output, categorically disposable, regenerated by tomorrow's session. Same shape as extract/ — content the pipeline-cron created and can recreate, not feature work owned by the agent. Production impact: 15 of 16 currently-stuck PRs are research-session verdict-deadlocks aged 8-12 days. Without this change they sit forever because the substantive_fixer can't classify (eval_issues=[] or mechanical-only) and the reaper allowlist excludes them. Once live, next hourly reaper cycle picks them up under the standard 24h-deadlock gate. Pattern choice: %/research-2* (date-suffix) over %/research-% (loose). Verified 15/15 stuck PRs match the tight pattern; sanity-check found rio/research-batch-agents-memory-harnesses (manually-named, not date- suffixed) which the loose pattern would catch and the tight pattern correctly excludes. Closed-status today, but a future hand-named research thesis branch sitting in request_changes for 24h would have been at risk. The date prefix '2' threads the needle until 2030 and ages naturally. Documented as an allowlist invariant ("disposable pipeline-generated branches") rather than a list, per Step 3 of the plan — future additions should match the invariant or update it explicitly. Verified live before pushing: - 15/15 currently stuck research PRs match the new pattern - Zero false positives on existing branch namespace (closed branches excluded by status='open' guard regardless) - Existing extract/ reweave/ fix/ allowlist members unchanged Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/substantive_fixer.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/substantive_fixer.py b/lib/substantive_fixer.py index c7537b0..907f6f2 100644 --- a/lib/substantive_fixer.py +++ b/lib/substantive_fixer.py @@ -647,10 +647,25 @@ async def verdict_deadlock_reaper_cycle(conn) -> int: return 0 # Two stuck-verdict shapes: leo:rc+domain:approve, leo:skipped+domain:rc. - # Branch allowlist scopes the reaper to disposable pipeline-managed branches. - # extract/, reweave/, fix/ are content the pipeline created and can recreate; - # agent branches (theseus/, vida/, epimetheus/, etc.) are WIP feature work - # and must not be reaped — owners review their own PRs on their own cadence. + # + # Branch allowlist invariant: the reaper closes ONLY disposable, pipeline- + # generated branches — content the pipeline (or a daily cron) created and + # can recreate. Three classes qualify: + # + # extract/* — per-source extraction PRs, regenerated next ingest cycle + # reweave/* — nightly graph-edge maintenance, regenerated next reweave + # fix/* — pipeline-internal fix branches + # */research-2* — daily {agent}/research-{YYYY-MM-DD} sessions; the date + # suffix scopes this to cron-generated outputs only and + # excludes hand-named research branches like + # rio/research-batch-agents-memory-harnesses, which are + # feature work owned by the agent. + # + # WIP agent feature branches (theseus/feature-foo, epimetheus/some-fix, + # rio/research-thesis-name) are NEVER reaped — owners review their own PRs + # on their own cadence. The */research-2* pattern threads the needle: it + # picks up daily synthesis output that the agent will regenerate tomorrow + # while leaving manually-named research work alone. rows = conn.execute( """SELECT number, branch, eval_issues, leo_verdict, domain_verdict, last_attempt, fix_attempts @@ -659,7 +674,10 @@ async def verdict_deadlock_reaper_cycle(conn) -> int: AND tier0_pass = 1 AND last_attempt IS NOT NULL AND last_attempt < datetime('now', ? || ' hours') - AND (branch LIKE 'extract/%' OR branch LIKE 'reweave/%' OR branch LIKE 'fix/%') + AND (branch LIKE 'extract/%' + OR branch LIKE 'reweave/%' + OR branch LIKE 'fix/%' + OR branch LIKE '%/research-2%') AND ( (leo_verdict = 'request_changes' AND domain_verdict = 'approve') OR (leo_verdict = 'skipped' AND domain_verdict = 'request_changes')