Rebase an existing pull-request branch onto its moved base branch, resolving conflicts with judgment while preserving authored history and never leaving a conflict marker. Use on a follow-up dispatched because the PR now conflicts with its base.
Install
npx skillscat add hallmanac/hall9k/rebase-onto-main Install via the SkillsCat registry.
Rebase onto main
Bring a pull-request branch current with its base branch after other work merged ahead of
it (AGENTS.md Git rules; PLAN.md Decisions Log #26 and backlog 44). This is the inverse ofabsorb-review-fixes: that skill folds new fixes into existing commits, this one replays
existing commits onto a new base. Both share the same doctrine: authored history stays
intact and the agent never pushes. What differs is how each verifies before that: a fixup
rebase diffs against its own pre-rebase tip, but a rebase onto a moved base has no such tip
to diff against — see step 5 for how this one verifies instead.
Precondition: the working tree is an existing PR branch that GitHub reports asCONFLICTING against its base (gh pr view --json mergeStateStatus,mergeable, or the
follow-up's own dispatch reason). If the branch is merely behind but still mergeable,
this skill is not needed; GitHub merges it fine as-is.
Process
Confirm the base and the conflict.
git fetch origin, thengit log --oneline origin/<base>..HEADto see this branch's own commits andgit log --oneline HEAD..origin/<base>to see what the base gained.Rebase, replaying this branch's own commits:
git rebase origin/<base>This is a plain rebase, not fixup/autosquash: the branch's commits already exist and are
being replayed onto a new base, so their authorship and the shape of the branch's history
are preserved by the rebase itself. There is nothing to squash and no new commit to
create for "the rebase" as an operation.Resolve each conflict with judgment, not mechanically. Read what each side actually
changed and why before touching a hunk:- Keep both when the two changes are independent and both still make sense together
(the common shape: a shared file both branches added to in different places). - Pick a side when one change genuinely supersedes or invalidates the other (a
rename, a signature change, a rule the other side's addition now violates), and say
which side won and why in your summary. - Park, do not guess, when both sides changed the same behavior, not just the same
lines, and picking either would silently drop real work. See "When a conflict is not
yours to resolve" below.
PLAN.md's own §16 v0 Decisions Log tail is a conflict of two different shapes, and only
one of them is left for the mechanical step. Either way, it is always keep both: this
branch's own entry stays exactly as written, ordered after whatever main gained. Which shape
decides whether you also renumber it yourself, right here:This branch's own tail entry carries
PLACEHOLDER-<shortid>. Never renumber it by
hand. The mechanical pre-final-pass rebase step (DecisionsLogRenumberer, invoked
automatically once this rebase lands, no agent session) assigns the real number — a
hand-picked one here would just be a second, competing guess for it to untangle.This branch's own tail entry already carries a real, hand-picked number that collides
with an entry main gained (a branch cut before the placeholder convention shipped).
Whether you renumber it yourself here depends on which of this skill's two invocation paths
put you in front of this conflict — they leave the run in different states, andDecisionsLogRenumberer's transition-shape check reads that state, not just the diff:- Dispatched as this skill's own documented precondition — a standalone follow-up
because GitHub reports the PRCONFLICTINGagainst its base (the case the precondition
above describes). Renumber it yourself, right here, the same way the pre-convention
repository always did: give it the log's next free number and append a hand-written
"Renumbering placement note" (PLAN.md's own §16 already carries about twenty of these;
match their style) recording the old number, the collision, and which citations elsewhere
in the repository moved with it. Do NOT leave this one forDecisionsLogRenumbereron
this path — its transition-shape check reads against this branch's own fork point, but by
the time it runs your owngit rebase origin/<base>in step 2 has already landed, so the
branch's merge-base againstorigin/<base>is noworigin/<base>'s own tip, which
already contains the number your entry collided with. A standalone follow-up's run
carries no prior pre-final-pass rebase-recovery event, so the check has nothing else to
read and falls back to that freshly-advanced merge-base, sees "already taken at the fork
point" — the signature of a genuine hand-numbering mistake, not a parallel merge — and
declines. Left unrenumbered, the duplicate only failsDecisionsLogNumberingGuardTests
at the mandatory final pass, and nothing mechanical will ever fix it from there. - Invoked from inside an active run's own pre-final-pass rebase-recovery session
(ReviewEngine'sEnsureRebasedBeforeFinalPassAsynchit this same conflict mid-run and
dispatched you to resolve it, rather than this being a standalone follow-up). Do NOT
renumber by hand here — leave it forDecisionsLogRenumberer. This path is not the one
the paragraph above describes: the run already carries a recordedLastPreFinalPassRebaseFromCommitfrom before this conflict, whichRunAggregate's own
trailing-no-op guard keeps pointing at the branch's TRUE original fork point across this
re-entry, and the check reads that instead of the freshly-advanced merge-base — so it
correctly sees the collision as a parallel merge, not a hand-numbering mistake, and
renumbers on its own once this rebase lands. Hand-renumbering here would race the
mechanical step and hand citation-rewriting to judgment instead of the sweep AGENTS.md's
own "no agent renumbers it" rule exists to guarantee.
- Dispatched as this skill's own documented precondition — a standalone follow-up
Land a resolved conflict inside the commit being replayed (
git add <files>thengit rebase --continue), never as a separate "resolve conflict" commit. The mapping
ruleabsorb-review-fixesuses (a change belongs to the commit that owns it) applies
here too, and here the owner is unambiguous: it is the commit currently being replayed.- Keep both when the two changes are independent and both still make sense together
Never leave a conflict marker in a commit. Before every
git rebase --continue, grep
the files you just resolved for<<<<<<<,=======,>>>>>>>. Origin incident
(2026-08-21): a conflict-marker string landed inside a committed file during a hand
rebase and had to be caught and fixed as its own follow-up. The platform's discipline
since is that a marker in a commit is never acceptable, checked before it can happen
rather than caught after.Verify tree behavior, not just tree identity. Unlike a fixup rebase, this one
deliberately changes content (that is the point), so there is no pre-rebase tip to diff
against. What there is to verify: run the project's own verification gates against the
rebased tree before finishing (the build, the test suite, whatever this project
configures). Origin incident (2026-08-22): this very feature's own first retry died on 7
test failures that were main-reconciliation fallout from combining two branches' changes,
not flakiness; each side had compiled and passed alone, and only the combination broke.
A rebase that resolves every textual conflict cleanly can still be behaviorally wrong;
only running the gates catches that.Do not push. In a Hall9k follow-up run, the daemon pushes the rebased branch with
git push --force-with-leaseafter re-verifying (Decisions Log #26), the same push path
every other follow-up rewrite uses. Push yourself only when working by hand:git push --force-with-lease
When a conflict is not yours to resolve honestly
Some conflicts are not a merge mechanic; they are two people's work disagreeing about what
the code should do. If both sides changed the same behavior (not just the same lines) and
you cannot honestly tell which should win: resolve every conflict you can first, then stop.
Abort the rebase if you have not finished it (git rebase --abort), so the branch is left in
its last-known-good state rather than half-rebased, then close your summary with a line
reading exactly
RESOLUTION: disputedAbove it, name every conflicting file, what each side changed and why (read both
commits' own history, not just the diff), and what you would do instead and why. When this
skill is running inside a Hall9k follow-up run, that marker parks the run with your text
saved beside it and nothing is pushed until a human decides (h9k review resolve). One
honest attempt per conflict, not a negotiation: never park twice over the SAME conflict a
previous attempt already disputed. If a human resolves that dispute and the resumed attempt
hits a genuinely undecidable DIFFERENT conflict, park again: that is not a second
negotiation over the first one, and picking a side instead to avoid a second park would
silently drop one side's work.
When every conflict is resolved and the gates pass, close your summary with
RESOLUTION: fixedinstead, plus a short account of what conflicted and how each was resolved.
Origin incidents
- 2026-08-22, PR #26 (the incident this skill exists for). A pull request sat
AwaitingReviewwith every review thread resolved and nothing left but approval, and was
silently unmergeable, because three other PRs had merged into main after its branch was
cut. Nothing observed the conflicting state, nothing surfaced it, and a human ended up
doing the rebase by hand: five keep-both conflict stops across a shared seam. That toil,
and the fact that it recurs on every project with more than one lane merging, is why the
platform now detects and dispatches this automatically (CloseoutEngine,PullRequestConflictObserved) instead of leaving it for a human to notice. - 2026-08-22, the same feature's own build. The first retry of implementing this very
capability failed 7 tests that looked like flakiness and were not; they were the fallout
of combining two branches' changes, surfaced only by actually running the suite after the
rebase. Step 5 above exists because of exactly this. - 2026-08-21, marker-commit incident. A hand-resolved conflict landed with a stray
conflict-marker string still inside a committed file. Step 4 above is the check that
catches this before a commit exists, not after.