← Catalog

Update From Branch

Sync the current branch from another (usually main) via merge or rebase, auto-stashing dirty work and surfacing conflicts clearly — never discards changes. Any repo.

skillportablecrossworkflow

How to use it

Comes with the ai-devkit plugin (install — two commands). Once installed, type this in any repo:

/update-from-branch main

You can also just describe the task in plain words — the agent picks the skill up by itself. That’s the Claude Code form — in Codex, type $update-from-branch instead, or just name the skill in plain words.

What a run looks like:

That’s all you do — the agent runs the whole workflow itself. Curious, or want to audit it? The playbook it follows is collapsed under Under the hood below.

Under the hood — the playbook the agent follows nothing in here is for you to run

Everything in this section is read and executed by the agent when you invoke the skill. It’s published so you can audit it, learn from it, or adapt it — not because you need to follow it yourself.

Bring the latest changes from another branch (usually main) into the branch you're on, without ever losing uncommitted work. The skill stashes dirty changes, fetches, applies via merge or rebase, restores the stash, and reports any conflicts with clear next steps. Safety-first: it shows what will happen before doing it and never discards anything.

Source Branch (optional):#

$ARGUMENTS

Workflow#

Phase 1: Orient + Detect the Source#

CURRENT=$(git branch --show-current)

# Source to sync FROM: the argument, else the detected base branch.
SOURCE="$ARGUMENTS"
if [ -z "$SOURCE" ]; then
  if git ls-remote --heads origin develop | grep -q .; then SOURCE=develop
  else SOURCE=$(git remote show origin 2>/dev/null | sed -n 's/.*HEAD branch: //p'); SOURCE=${SOURCE:-main}; fi
fi

echo "Updating '$CURRENT' from '$SOURCE'"

Guard rails — refuse politely if:

  • CURRENT is SOURCE (nothing to sync — you're on the branch you'd merge from).
  • There's a merge/rebase already in progress (git status shows it) — tell the user to finish or --abort it first.

Phase 2: Protect Dirty Work (auto-stash)#

DIRTY=0
if [ -n "$(git status --porcelain)" ]; then
  DIRTY=1
  git stash push --include-untracked -m "update-from-branch: WIP on $CURRENT"
  echo "Stashed uncommitted changes (including untracked). They will be restored after the update."
fi

The work is parked in a named stash — it is never thrown away, even if later steps fail.

Phase 3: Fetch the Latest#

git fetch origin "$SOURCE"   # syncs from the REMOTE origin/$SOURCE (the common case, e.g. main)

# How far apart are we? (behind = commits to pull in; ahead = your local commits)
BEHIND=$(git rev-list --count "HEAD..origin/$SOURCE")
AHEAD=$(git rev-list --count "origin/$SOURCE..HEAD")
echo "Behind origin/$SOURCE by $BEHIND · ahead by $AHEAD"

If BEHIND is 0, you're already up to date — restore the stash (Phase 5) and stop.

Phase 4: Choose Strategy + Apply#

Decide merge vs rebase and state the choice before running it:

  • Rebase — replays your local commits on top of $SOURCE for a clean, linear history. Best when your branch is unpushed or solo. Never rebase commits others have already pulled.
  • Merge — records a merge commit, preserving true history and the original commits. Best when the branch is shared/pushed, or your team prefers explicit merge topology.
Default heuristic: if the branch has an upstream and may be shared (git rev-parse --abbrev-ref @{u} succeeds and others could have it), prefer merge; for a private, unpushed branch, rebase is fine. When unsure, ask once, then default to merge (the non-rewriting, safer choice).
# --- Rebase path ---
git rebase "origin/$SOURCE"

# --- Merge path ---
git merge --no-edit "origin/$SOURCE"

Phase 5: Restore Dirty Work#

Only after the update lands cleanly:

if [ "$DIRTY" = "1" ]; then
  git stash pop || echo "Stash pop hit conflicts — your WIP is safe in 'git stash list'; resolve, then 'git stash drop'."
fi

Phase 6: Conflict Handling + Report#

If merge/rebase or the stash pop reports conflicts, stop and guide — do not attempt blind auto-resolution:

git status --short | grep '^UU\|^AA\|^DD'   # conflicted paths
git diff --name-only --diff-filter=U         # files needing resolution

Tell the user, per path, the resolution loop and the abort escape hatch:

Conflicts in:
  src/api/client   ← edit to resolve, then: git add src/api/client
  config           ← edit to resolve, then: git add config

After resolving all:   git rebase --continue   (or: git commit, for a merge)
To back out entirely:  git rebase --abort       (or: git merge --abort)
Your stashed WIP (if any) remains in: git stash list

Final summary (clean case):

✅ '<current>' updated from '<source>'
   Strategy: <merge|rebase> · pulled <N> commit(s)
   WIP restored: <yes|none>

Quick Reference#

Merge vs rebase#

MergeRebase
HistoryPreserves topology, adds merge commitLinear, rewrites your commits
Use whenBranch is shared/pushedBranch is private/unpushed
RiskExtra merge commitsNever rewrite shared history

Safety invariants#

  • Dirty work is stashed, never discarded — recover from git stash list.
  • Show the plan (behind/ahead, chosen strategy) before applying.
  • On conflict: surface paths + resolution + abort command; never force.
  • Refuse if a merge/rebase is already in progress.

Escape hatches#

Abort in-progress: git rebase --abort / git merge --abort. · Recover WIP: git stash listgit stash pop.

Adapt it to another stack ready-made prompt for Claude / Codex

Adapt to your platform

Copy this prompt into Claude or Codex, fill in your stack, and it will generate an adapted version for your project.

Port an agent skill (Claude Code / Codex SKILL.md format) to my project.

Below is "update-from-branch" from ai-devkit (origin platform: cross, portability: portable).
What it does: Sync the current branch from another (usually main) via merge or rebase, auto-stashing dirty work and surfacing conflicts clearly — never discards changes. Any repo.

Read it, then produce an equivalent SKILL.md (plus any scripts) for MY project:
<describe your stack, conventions, and repo here>

Keep the workflow's structure and intent. Replace platform-specific tooling and references per these notes:
Generic and safety-first. Pick the default merge-vs-rebase choice to match your team's history policy (rebase for linear history; merge to preserve true topology and never rewrite shared commits). The base-branch detection and auto-stash are portable as-is.

List what you changed and why.

--- SKILL.md ---
# Update From Branch

Bring the latest changes from another branch (usually `main`) into the branch you're on, **without ever losing uncommitted work**. The skill stashes dirty changes, fetches, applies via merge or rebase, restores the stash, and reports any conflicts with clear next steps. Safety-first: it shows what will happen before doing it and never discards anything.

## Source Branch (optional):
$ARGUMENTS

## Workflow

### Phase 1: Orient + Detect the Source

```bash
CURRENT=$(git branch --show-current)

# Source to sync FROM: the argument, else the detected base branch.
SOURCE="$ARGUMENTS"
if [ -z "$SOURCE" ]; then
  if git ls-remote --heads origin develop | grep -q .; then SOURCE=develop
  else SOURCE=$(git remote show origin 2>/dev/null | sed -n 's/.*HEAD branch: //p'); SOURCE=${SOURCE:-main}; fi
fi

echo "Updating '$CURRENT' from '$SOURCE'"
```

Guard rails — refuse politely if:
- `CURRENT` **is** `SOURCE` (nothing to sync — you're on the branch you'd merge from).
- There's a merge/rebase already in progress (`git status` shows it) — tell the user to finish or `--abort` it first.

### Phase 2: Protect Dirty Work (auto-stash)

```bash
DIRTY=0
if [ -n "$(git status --porcelain)" ]; then
  DIRTY=1
  git stash push --include-untracked -m "update-from-branch: WIP on $CURRENT"
  echo "Stashed uncommitted changes (including untracked). They will be restored after the update."
fi
```

The work is parked in a named stash — it is never thrown away, even if later steps fail.

### Phase 3: Fetch the Latest

```bash
git fetch origin "$SOURCE"   # syncs from the REMOTE origin/$SOURCE (the common case, e.g. main)

# How far apart are we? (behind = commits to pull in; ahead = your local commits)
BEHIND=$(git rev-list --count "HEAD..origin/$SOURCE")
AHEAD=$(git rev-list --count "origin/$SOURCE..HEAD")
echo "Behind origin/$SOURCE by $BEHIND · ahead by $AHEAD"
```

If `BEHIND` is 0, you're already up to date — restore the stash (Phase 5) and stop.

### Phase 4: Choose Strategy + Apply

Decide **merge vs rebase** and state the choice before running it:

- **Rebase** — replays your local commits on top of `$SOURCE` for a clean, linear history. Best when your branch is **unpushed or solo**. Never rebase commits others have already pulled.
- **Merge** — records a merge commit, preserving true history and the original commits. Best when the branch is **shared/pushed**, or your team prefers explicit merge topology.

> Default heuristic: if the branch has an upstream and may be shared (`git rev-parse --abbrev-ref @{u}` succeeds and others could have it), prefer **merge**; for a private, unpushed branch, **rebase** is fine. When unsure, ask once, then default to merge (the non-rewriting, safer choice).

```bash
# --- Rebase path ---
git rebase "origin/$SOURCE"

# --- Merge path ---
git merge --no-edit "origin/$SOURCE"
```

### Phase 5: Restore Dirty Work

Only after the update lands cleanly:

```bash
if [ "$DIRTY" = "1" ]; then
  git stash pop || echo "Stash pop hit conflicts — your WIP is safe in 'git stash list'; resolve, then 'git stash drop'."
fi
```

### Phase 6: Conflict Handling + Report

If merge/rebase or the stash pop reports conflicts, **stop and guide** — do not attempt blind auto-resolution:

```bash
git status --short | grep '^UU\|^AA\|^DD'   # conflicted paths
git diff --name-only --diff-filter=U         # files needing resolution
```

Tell the user, per path, the resolution loop and the abort escape hatch:

```
Conflicts in:
  src/api/client   ← edit to resolve, then: git add src/api/client
  config           ← edit to resolve, then: git add config

After resolving all:   git rebase --continue   (or: git commit, for a merge)
To back out entirely:  git rebase --abort       (or: git merge --abort)
Your stashed WIP (if any) remains in: git stash list
```

Final summary (clean case):
```
✅ '<current>' updated from '<source>'
   Strategy: <merge|rebase> · pulled <N> commit(s)
   WIP restored: <yes|none>
```

## Quick Reference

### Merge vs rebase
| | Merge | Rebase |
|---|---|---|
| History | Preserves topology, adds merge commit | Linear, rewrites your commits |
| Use when | Branch is shared/pushed | Branch is private/unpushed |
| Risk | Extra merge commits | Never rewrite shared history |

### Safety invariants
- Dirty work is **stashed**, never discarded — recover from `git stash list`.
- Show the plan (behind/ahead, chosen strategy) before applying.
- On conflict: surface paths + resolution + abort command; never force.
- Refuse if a merge/rebase is already in progress.

### Escape hatches
Abort in-progress: `git rebase --abort` / `git merge --abort`. · Recover WIP: `git stash list` → `git stash pop`.