Repository navigation
A failed Self-merge goes back round the Repair loop; policy refusals leave the PR ready - #97
Merged
Merged
Conversation
A Merge run whose merge fails now goes round again, merging the Base branch, repairing conflicts and red CI within the Run's budgets, and retries the merge on the new green head. A round that finds nothing to fix is a policy refusal: the Run fails but leaves the PR ready for review. The fake gh can refuse merges and run a script at merge time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A policy refusal no longer pushes an empty failed-run commit, which left the ready PR on a head whose CI nobody watched. The round after a failed merge now returns a Round enum, and CONTEXT.md defines Policy refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Sep 27, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
In a Merge run, a failed Self-merge now goes back round the Repair loop, with the same budgets, and is tried again on the new green head. If that round finds nothing to fix, it is a Policy refusal.
implement run_session("implement") watched = repair_loop.run() - ensure_pr_ready_and_mergeable - if Merged: github::merge(watched)? # any failure → Failed run, draft + loop + ensure_pr_ready_and_mergeable + if ReadyForReview or github::merge(watched) ok → done + round_after_failed_merge(watched) + origin/<base> moved since? → count a base move + repair_loop.run() # conflict / CI-fix Repairs, same budgets + NewHead(head) → watched = head; try again + NothingToFix → Err(merge error + PolicyRefusal)failed_run::failsees aPolicyRefusaland does two things:The Run exits 1, prints the PR URL on stdout and puts GitHub's error on stderr. thirdshift never parses GitHub's error text: "nothing to fix" means the round ended on the same head with no Repair and no base move.
What else changed:
Budgetsstruct insideRepairLoop, so rounds and Repairs after a failed merge count against the Run's existing caps.origin/<base> kept moving: merged it again 5 times, because it covers both causes.gh: three new hooks:gh fake refuse-merges <times> '<error>'gh fake on-merge <times> '<script>', which moves the Base branch at merge time.$FAKE_CI_SHAforon-ci-readscripts.Evidence
tests/merge_run.rs, and all 5 failed on the old code.After: the full suite passes (
cargo test), and so doescargo clippy --all-targets.I checked the last assertion (no failure commit pushed on a refusal) both ways: it fails without the
failed_run.rschange and passes with it.Merge Danger
Door: two-way
Blast Radius: Merge-runs
Unaddressed findings
Standards
PolicyRefusalis found through anyhow's context downcast, not throughfail's signature. I kept it. It is a documented public type infailed_run, andfail's doc names it. Threading an outcome enum throughrunwould widen every error path for a single case.RepairLoop. Skipped.implementowns the Run's goal (ready vs merged), andRepairLoopstays goal-agnostic.Budgets. Kept. A Repair or a base move is exactly what the check means, and comparing the counters states that directly.RepairLoop::rundestructuresselfinto locals. Kept. The locals are for readability, and the struct is there to carry the budgets across rounds.tests/merge_run.rsduplicate those intests/ci.rsandtests/mergeable.rs. Skipped. That pattern already exists; a shared test-support refactor is out of scope here.Spec
ghhas more thanrefuse-merges: it also hason-mergeand$FAKE_CI_SHA. These are needed to move the Base branch at merge time and to turn CI red on a head that doesn't exist yet. The acceptance tests for the conflict and CI cases depend on them.Closes #93
🤖 Generated with Claude Code