Compare commits

...

1 commit

Author SHA1 Message Date
545232f61e ops(scripts): clear-stale-ref-pr-5224 reviewable cleanup script
PR #5224 (clay/research-2026-04-29) has dual-approve verdicts but stalled in
status='conflict' on a stale-ref ff-push failure. conflict_rebase_attempts hit
the 3-attempt cap. The failure is mechanical (main moved during merge), not
content — eval gate already passed, cherry-pick onto fresh main HEAD should
resolve.

Script resets the conflict state so the merge cycle picks the PR back up:
  - status: 'conflict' -> 'approved'
  - conflict_rebase_attempts: N -> 0
  - last_error: cleared
  - audit_log entry: stage='ops', event='pr_5224_stale_ref_reset'

Two safety guards:
  - Branch must match clay/research-2026-04-29 (refuses to rewrite an
    unrelated PR if #5224 was renumbered)
  - Both verdicts must be 'approve' (refuses to set status='approved'
    on a PR the eval gate didn't pass)

Idempotent. Mirrors reset-m3taversal-sourcer.py shape per the
reviewable-mutations standing rule (Ganymede, Apr 27).

Per Ganymede observation #3 in the May 10 reaper-allowlist review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 19:41:17 +01:00

View file

@ -0,0 +1,129 @@
#!/usr/bin/env python3
"""Reset PR #5224 (clay/research-2026-04-29) for merge retry after stale-ref conflict.
Background: PR #5224 has dual-approve verdicts (leo:approve, domain:approve) but
sat in status='conflict' with last_error showing a stale-ref ff-push failure:
"local ff-push failed: cannot lock ref 'refs/heads/main': is at 28e6fa93 but
expected efd613a6". Main moved during the merge attempt, conflict_rebase_attempts
hit the 3-attempt cap, and the PR stalled.
The dual-approve verdicts mean the eval gate already passed the failure was
purely mechanical (ff-push race, not content-related). Cherry-pick onto a fresh
main HEAD should resolve cleanly.
This script resets the conflict state so the merge cycle picks the PR back up:
- status: 'conflict' 'approved'
- conflict_rebase_attempts: N 0
- last_error: cleared
- audit_log entry: stage='ops', event='pr_5224_stale_ref_reset'
If cherry-pick fails again on retry, merge cycle will set status='conflict' with
a fresh last_error, and we close the PR as stale (downstream clay sessions on
2026-04-30, 2026-05-09 etc. have already merged similar content).
Per Ganymede observation #3 (May 10): DB mutations go through reviewable code
paths. Audit trail benefits from one-shape-per-commit.
Idempotent safe to re-run. If status is already 'approved' (or merged/closed),
prints current state and exits without writing.
Usage:
python3 scripts/clear-stale-ref-pr-5224.py --dry-run
python3 scripts/clear-stale-ref-pr-5224.py
"""
import argparse
import json
import os
import sqlite3
import sys
from pathlib import Path
DB_PATH = os.environ.get("PIPELINE_DB", "/opt/teleo-eval/pipeline/pipeline.db")
PR_NUMBER = 5224
EXPECTED_BRANCH = "clay/research-2026-04-29"
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args()
if not Path(DB_PATH).exists():
print(f"ERROR: DB not found at {DB_PATH}", file=sys.stderr)
sys.exit(1)
conn = sqlite3.connect(DB_PATH, timeout=30)
conn.row_factory = sqlite3.Row
row = conn.execute(
"""SELECT number, branch, status, leo_verdict, domain_verdict,
conflict_rebase_attempts, last_error
FROM prs WHERE number = ?""",
(PR_NUMBER,),
).fetchone()
if not row:
print(f" No PR row for #{PR_NUMBER} — nothing to reset.")
return
print(f" PR #{row['number']} ({row['branch']})")
print(f" status={row['status']} leo={row['leo_verdict']} domain={row['domain_verdict']}")
print(f" conflict_rebase_attempts={row['conflict_rebase_attempts']}")
if row["last_error"]:
print(f" last_error={row['last_error'][:140]}")
# Sanity: branch must match. If someone reused PR #5224 number we don't want
# to rewrite an unrelated PR's state.
if row["branch"] != EXPECTED_BRANCH:
print(
f" ABORT: branch mismatch — expected {EXPECTED_BRANCH}, "
f"got {row['branch']}. Refusing to write."
)
sys.exit(2)
# Sanity: dual-approve must hold. Reset is only safe when eval gate already passed.
if not (row["leo_verdict"] == "approve" and row["domain_verdict"] == "approve"):
print(
f" ABORT: PR is not dual-approve "
f"(leo={row['leo_verdict']}, domain={row['domain_verdict']}). "
"Refusing to reset to 'approved' status."
)
sys.exit(2)
if row["status"] in ("approved", "merging", "merged", "closed"):
print(f" Already at status={row['status']} — no-op.")
return
if args.dry_run:
print(
" (dry-run) UPDATE prs SET status='approved', "
"conflict_rebase_attempts=0, last_error=NULL WHERE number=5224"
)
return
conn.execute(
"""UPDATE prs SET
status = 'approved',
conflict_rebase_attempts = 0,
last_error = NULL,
last_attempt = datetime('now')
WHERE number = ?""",
(PR_NUMBER,),
)
conn.execute(
"""INSERT INTO audit_log (timestamp, stage, event, detail)
VALUES (datetime('now'), 'ops', 'pr_5224_stale_ref_reset', ?)""",
(json.dumps({
"pr": PR_NUMBER,
"branch": EXPECTED_BRANCH,
"from_status": row["status"],
"from_conflict_rebase_attempts": row["conflict_rebase_attempts"],
"previous_error": (row["last_error"] or "")[:200],
"rationale": "stale-ref ff-push race; dual-approve eval already passed; retry cherry-pick on fresh main",
}),),
)
conn.commit()
print(" Reset applied. Merge cycle will pick up on next interval (~30s).")
if __name__ == "__main__":
main()