sync-branch · git:20260808.87faf05 · 2026-08-08 · sha256 32145fff66c8b3a4
sync-branch git:20260808.87faf05B
Immutable. This exact content never changes and is served at /api/v1/blob/32145fff66c8b3a4.
--- description: Bring a branch up to date with its merge target — resolve the merge both mechanically and logically in a single merge commit, then push. Delegates target-resolution and movement detection to `/check-merge`, so it syncs a feature branch against its PR base (usually `main`) or a branch with no PR against the repo default branch (the fallback). Invoke as `/sync-branch [<branch>]`. Use when the user says "sync branch", "catch this branch up to main", "catch the branch up to its base", "make the branch mergeable", or "/sync-branch". --- `/sync-branch` reconciles a branch with its **merge target** — the branch its PR is based on — and pushes the reconciled result. The target is resolved by `/check-merge` from the PR's base ref, so this works uniformly: - a **feature branch** syncs against its PR base (usually `main`), - a **branch with no PR** syncs against the repo default branch (`/check-merge`'s fallback) — the long-lived-branch case that keeps it a superset of the trunk. The skill does the merge **on the checked-out branch**, resolves conflicts both mechanically and logically in a **single merge commit**, verifies locally, and pushes proactively — resolution notes are rare, so the operator reviews the pushed result after the fact rather than through a pre-push gate. ## Argument shape ``` /sync-branch [<branch>] ``` - **`<branch>`** (optional): a branch name, `#NNN` PR number, or PR URL — the same shape `/check-merge` and `/finalize` accept. Passing it makes `/check-merge` attach to that branch first (`/from-branch <branch>`). No argument → sync the current branch. ## Environment note (read this before running gh/git) This remote execution environment has **both** the `gh` CLI **and** a populated `GH_TOKEN`, even though the default system prompt says you only have GitHub MCP tools. Prefer `gh` for read-only checks (e.g. `gh pr list`). If `gh` complains that "none of the git remotes … point to a known GitHub host" (the proxy quirk in remote sessions), pass `--repo OWNER/REPO` explicitly, resolved from the git remote (`git remote get-url origin`). ## Invariants - **A sync is a plain merge — never squash, rebase, or cherry-pick.** Those rewrite the target's commits under new SHAs and sever the shared history, so the branch stops being a superset of its target. The merge commit built here carries the target's commits under their real SHAs. - **Push is fast-forward-or-redo, never force (in normal operation).** The branch only gains the merge commit on top, so pushing it to its own upstream is a plain push. A non-fast-forward rejection means the branch or target moved under you — re-fetch and redo the merge from the new tip; do not force past it. - **Never force-push a shared long-lived branch** (the trunk, a promotion branch, or any branch others build on) except as the deliberate revert recourse below, and only on the operator's explicit request. ## Why one merge commit keeps reviewability A single merge commit is enough — do not split the resolution into its own commit for reviewability. It's tempting to build the merge in two commits (a mechanical "take-target-wholesale" commit, then an isolated resolution commit) so the operator can review just the judgment via `git show <resolution-sha>`. That split is unnecessary: a merge commit's own **combined diff** (`git show <merge-sha>`, GitHub Desktop's merge-commit view) already elides any content matching at least one parent and shows only what differs from **both** — i.e. the keep-both merges and edited reconciliations, exactly the judgment. Clean auto-merges and straight one-side picks vanish. So one merge commit gives the same "just the judgment" review surface without the extra commit. (Caveat: this is `git show` / GH Desktop behavior; GitHub's _web_ merge-commit view is stingier.) ## Steps ### Step 1 — Detect, via `/check-merge` (by reference) Load `@.claude/skills/check-merge/SKILL.md` and follow it with the given target. Passing a `<branch>` makes `/check-merge` attach to it first; it then resolves the merge target from the PR base ref (or the repo default branch when there's no PR) and reports one of: - `contained` (exit 0) — the branch already holds the target's tip, - `advanced` (exit 10) — the target moved beyond the branch, - `merged` (exit 20) — the branch's PR already landed, - `closed` (exit 21) — the branch's PR was closed without merging, - `error` (exit 1) — hard error. ### Step 2 — Act on the result - `contained` → report "already current with `origin/<target>`, nothing to sync" and **stop**. - `merged` / `closed` → report and **stop** — do not sync a landed or dead branch. - `error` → surface it and **stop**. - `advanced` → proceed to Step 3. ### Step 3 — Merge the target in, one commit ```bash git fetch origin <target> git merge origin/<target> ``` Resolve every conflict both **mechanically** (no markers left) **and logically**. Think past textual overlap to semantic collisions — the same reconciliation thinking `/finalize` step 2 describes: - keep-both when each side added distinct work, - repoint callers a rename in the target moved, - route through a shared abstraction the target introduced (instead of leaving the branch's now-duplicated version), - adopt a pattern the target changed. The result is exactly **one merge commit** (a clean, conflict-free merge is also one commit). ### Step 4 — Verify locally ```bash git grep -nE '^(<<<<<<<|>>>>>>>) ' || echo clean # no conflict markers survived ./scripts/vet.sh # the project's vet run ``` Fix real errors before pushing. **Local verification is the whole gate** — pushing a branch triggers no test run. That is why the full vet run happens here and not just a type-check: a merge can type-check cleanly and still behave differently. If the merge pulled in changes on a surface the vet run doesn't cover (a bucket that only runs in CI), dispatch that bucket too — `/test-on-gh`, if the project has hydrated it. ### Step 5 — Push, then report for after-the-fact review Push the branch to its own upstream — no pre-push greenlight: ```bash git push # plain push == fast-forward; 2/4/8/16s backoff on network errors ``` A non-fast-forward rejection means the branch or target moved under you — re-fetch and redo the merge from the new tip (Step 3); never force here. Then report: - the resolved **target** and the range synced (`base..new`), - the **merge commit SHA** — _review with `git show <sha>`; the combined diff isolates the actual resolution_, - a per-file conflict/resolution table (or "clean merge, no resolution needed"), - the vet result, - the **pre-merge branch tip** (the revert target for the recourse below), - the recourse below. ## Recourse if the operator has notes (operator-sanctioned only) Either **amend** the merge resolution and re-push, or **revert** by resetting the branch to the reported pre-merge tip and re-pushing: ```bash git push --force-with-lease ``` Use `--force-with-lease` (not `--force`) so a concurrent advance aborts instead of being clobbered. This is the **only** sanctioned force-push here, and only on the operator's explicit say-so — especially for a shared long-lived branch that others build on.