fix: fixer GC now closes PRs on Forgejo + deletes branches, not just DB

Root cause of 5-day pipeline stall: fixer GC marked PRs as closed in DB
but never synced to Forgejo. Branches stayed alive on remote, blocking
Gate 2 in batch-extract (branch exists → skip forever).

Now: GC fetches PR numbers, posts audit comment, closes on Forgejo,
deletes remote branch, THEN updates DB. Same pattern as _terminate_pr
in evaluate.py.

Pentagon-Agent: Epimetheus <3D35839A-7722-4740-B93D-51157F7D5E70>
This commit is contained in:
m3taversal 2026-03-24 14:37:50 +00:00
parent 0bedc43c94
commit d33ddd9f3d

View file

@ -225,15 +225,33 @@ async def fix_cycle(conn, max_workers=None) -> tuple[int, int]:
# Garbage collection: close PRs with exhausted fix budget that are stuck in open.
# These were evaluated, rejected, fixer couldn't help, nobody closes them.
# (Epimetheus session 2 — prevents zombie PR accumulation)
_gc = conn.execute(
"""UPDATE prs SET status = 'closed', last_error = 'fix budget exhausted — auto-closed'
# Bug fix: must also close on Forgejo + delete branch, not just DB update.
# DB-only close caused Forgejo/DB state divergence — branches stayed alive,
# blocking Gate 2 in batch-extract for 5 days. (Epimetheus session 4)
gc_rows = conn.execute(
"""SELECT number, branch FROM prs
WHERE status = 'open'
AND fix_attempts >= ?
AND (domain_verdict = 'request_changes' OR leo_verdict = 'request_changes')""",
(config.MAX_FIX_ATTEMPTS + 2,), # GC threshold = mechanical + substantive budget
)
if _gc.rowcount > 0:
logger.info("GC: closed %d exhausted PRs", _gc.rowcount)
(config.MAX_FIX_ATTEMPTS + 2,),
).fetchall()
if gc_rows:
from .forgejo import api as _gc_forgejo, repo_path as _gc_repo_path
for row in gc_rows:
pr_num, branch = row["number"], row["branch"]
try:
await _gc_forgejo("POST", _gc_repo_path(f"issues/{pr_num}/comments"),
{"body": "Auto-closed: fix budget exhausted. Source will be re-extracted."})
await _gc_forgejo("PATCH", _gc_repo_path(f"pulls/{pr_num}"), {"state": "closed"})
if branch:
await _gc_forgejo("DELETE", _gc_repo_path(f"branches/{branch}"))
except Exception as e:
logger.warning("GC: failed to close PR #%d on Forgejo: %s", pr_num, e)
conn.execute(
"UPDATE prs SET status = 'closed', last_error = 'fix budget exhausted — auto-closed' WHERE number = ?",
(pr_num,),
)
logger.info("GC: closed %d exhausted PRs (DB + Forgejo + branch cleanup)", len(gc_rows))
batch_limit = min(max_workers or config.MAX_FIX_PER_CYCLE, config.MAX_FIX_PER_CYCLE)