# Promote Branch Pr

> Compare two branches in a remote GitHub repo, report the delta, and create a promotion PR if the target is behind. Works standalone; optionally hands off to babysit-github-pr after user confirmation.

- Skill: `0xmythril/promote-branch-pr` (Agent Skill)
- Install (CLI): `npx skillmds@latest add 0xmythril/promote-branch-pr`
- Raw SKILL.md: https://api.skillmd.com/api/skills/0xmythril/promote-branch-pr/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: 0xmythril (https://skillmd.com/u/0xmythril)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/0xmythril/promote-branch-pr

---


# Promote Branch → Pull Request

Compare two branches in a GitHub repo, create a promotion PR if the target is behind,
and optionally hand off to `babysit-github-pr` for monitoring.

## Parse inputs from the user's message

- `repo_url` (required) — full GitHub repo URL, e.g. `https://github.com/mocaverse/air-agent-services`
- `source` [default: `staging`] — branch to promote from
- `target` [default: `sandbox`] — branch to promote into
- `auto_babysit` [default: `false`] — if `true`, skip the confirmation prompt and immediately invoke `babysit-github-pr`

Any missing optional param falls back to the default above.

## Prerequisites — check before starting

1. `gh` CLI authenticated (`gh auth status`). If not available, fall back to `git` + `curl` with `GITHUB_TOKEN`.
2. The local repo does **not** need to be cloned. All operations work against the remote via `gh` API or `curl`.

## Extract owner/repo

```bash
OWNER_REPO=$(echo "$repo_url" | sed -E 's|.*github\.com[:/]||; s|\.git$||')
OWNER=$(echo "$OWNER_REPO" | cut -d/ -f1)
REPO=$(echo "$OWNER_REPO" | cut -d/ -f2)
```

## Step 1: Compare branches

**With gh:**

```bash
gh api repos/$OWNER/$REPO/compare/$target...$source --jq '.commits[] | "\(.sha[:7]) \(.commit.message | split("\n")[0])"' 2>/dev/null
COMMIT_COUNT=$(gh api repos/$OWNER/$REPO/compare/$target...$source --jq '.total_commits' 2>/dev/null)
```

**With curl (fallback):**

```bash
curl -s \
  -H "Authorization: token $GITHUB_TOKEN" \
  -H "Accept: application/vnd.github.v3+json" \
  "https://api.github.com/repos/$OWNER/$REPO/compare/$target...$source" \
  | python3 -c "
import sys, json
data = json.load(sys.stdin)
commits = data.get('commits', [])
print(f'Total commits: {len(commits)}')
for c in commits:
    msg = c['commit']['message'].split('\n')[0]
    print(f\"  {c['sha'][:7]} {msg}\")
" 2>/dev/null
COMMIT_COUNT=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/repos/$OWNER/$REPO/compare/$target...$source" | python3 -c "import sys,json; print(json.load(sys.stdin).get('total_commits',0))")
```

### Outcomes

| `COMMIT_COUNT` | Action |
|---|---|
| `0` or empty | Report `"$target is already up to date with $source. No PR needed."` and **exit**. |
| `> 0` | Continue to Step 2. |

## Step 2: Check for existing open PR

Search for an open PR with `head=$source` and `base=$target`.

**With gh:**

```bash
EXISTING=$(gh pr list --repo $OWNER/$REPO --head "$source" --base "$target" --state open --json number,url --jq '.[0]')
```

**With curl:**

```bash
EXISTING=$(curl -s \
  -H "Authorization: token $GITHUB_TOKEN" \
  "https://api.github.com/repos/$OWNER/$REPO/pulls?state=open&head=$OWNER:$source&base=$target" \
  | python3 -c "import sys,json; prs=json.load(sys.stdin); print(prs[0]['html_url'] if prs else '')")
```

If `EXISTING` is non-empty:
- Report the existing PR URL.
- Ask the user: `"An open PR already exists. Babysit it? (yes/no)"`
- If yes → invoke `babysit-github-pr` with `pr_url=$EXISTING`.
- **Exit** without creating a new PR.

## Step 3: Build PR body

Generate a promotion body that includes the commit list and a machine-readable footer.

```markdown
## Promote `source` → `target`

| Metric | Value |
|---|---|
| Commits ahead | COMMIT_COUNT |
| Source | `source` |
| Target | `target` |

**Commits included:**
- `abc1234` feat: add X
- `def5678` fix: resolve Y
- ...

---
*Generated by promote-branch-pr*
```

To build the commit list dynamically, reuse the output from Step 1.

## Step 4: Create the promotion PR

**With gh:**

```bash
gh pr create \
  --repo "$OWNER/$REPO" \
  --base "$target" \
  --head "$source" \
  --title "promote: $source → $target" \
  --body "$PR_BODY"
```

Capture the returned PR URL.

**With curl:**

```bash
PR_RESPONSE=$(curl -s -X POST \
  -H "Authorization: token $GITHUB_TOKEN" \
  -H "Accept: application/vnd.github.v3+json" \
  https://api.github.com/repos/$OWNER/$REPO/pulls \
  -d "{
    \"title\": \"promote: $source → $target\",
    \"body\": \"$PR_BODY\",
    \"head\": \"$source\",
    \"base\": \"$target\"
  }")

PR_URL=$(echo "$PR_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('html_url',''))")
```

If creation fails, report the error and **exit**.

## Step 5: Report and ask about babysitting

Report to the user:

```text
✅ Created promotion PR: PR_URL
   source → target | COMMIT_COUNT commits
```

Then ask:

> "PR created. Run babysit-github-pr to monitor it? (yes/no)"

  - If user says **yes** (or `auto_babysit=true`):
  - Invoke `babysit-github-pr` with `pr_url=$PR_URL`.
  - Pass sensible defaults: `duration=2h`, `interval=10m`.
  - **NEVER merge the PR yourself** — leave it merge-ready for the user to merge.
- If user says **no**:
  - Report `"Done. PR is open at PR_URL."` and **exit**.

## Agent autonomy boundary

- **Create** the PR if it doesn't exist.
- **Monitor** it if asked (via `babysit-github-pr`).
- **Resolve** merge conflicts, review comments, and CI failures.
- **Report** when the PR is merge-ready.
- **NEVER merge** the PR into the target branch. That action belongs to the user.


## Standalone guarantee

This skill never depends on `babysit-github-pr` being present. If the user declines babysitting, or the skill is unavailable, promotion still completes successfully. The handoff is strictly optional.

## Edge cases

| Scenario | Behavior |
|---|---|
| `source` == `target` | Report error, exit |
| `source` or `target` branch does not exist | Report error from GitHub API, exit |
| Open PR already exists | Report it, ask to babysit, do not duplicate |
| `COMMIT_COUNT == 0` | Report up-to-date, exit |
| PR creation fails (permissions, rules) | Report raw error, exit |
| PR is `CONFLICTING`/`DIRTY` | **Trivial/format drift only:** Resolve locally (see below), then re-check. **Non-trivial:** Halt and ask user for guidance. |
| `gh` missing and no `GITHUB_TOKEN` | Fail fast with auth setup instructions |

## Resolving trivial merge conflicts (format drift)

Promotion PRs often become `CONFLICTING` due to biome/format/style drift on `target` (e.g. sandbox had formatting-only changes that diverged from `source`). These are safe to resolve mechanically without human review.

### Detection
After finding an existing PR, check its merge state:
```bash
gh pr view $PR_NUM --repo $OWNER/$REPO --json mergeStateStatus,mergeable
```
If `mergeable: CONFLICTING` and the repo uses automated formatters (Biome, Prettier, ESLint), the conflicts are likely trivial.

### Resolution workflow

1. **Ensure the repo is cloned locally** and `origin` points to the remote:
   ```bash
   git fetch origin $source $target
   ```

2. **Checkout the source branch** and merge target into it:
   ```bash
   git checkout $source
   git merge origin/$target --no-edit
   ```

3. **Accept the source branch versions** for all conflicted files:
   ```bash
   git checkout --ours <file1> <file2> ...
   # or accept all conflicts programmatically:
   git diff --name-only --diff-filter=U | xargs git checkout --ours
   ```

4. **Commit and push**:
   ```bash
   git add -A
   git commit -m "chore: resolve $target→$source promotion conflicts"
   git push origin $source
   ```

5. **Re-check the PR** — it should flip to `MERGEABLE`.

### Pitfalls
- `--ours` vs `--theirs` is counterintuitive during a merge: `--ours` means the branch you checked out (source), `--theirs` means the branch you merged in (target). For promotion PRs, always accept **source** (`--ours`) because source is the source of truth.
- Do NOT use `--force` when pushing the resolved source branch.
- If conflicts are in non-formatting files (logic, types, schemas), stop and ask the user before resolving.

See `references/resolve-promotion-conflicts.md` for a worked example from the `mocaverse/air-agent-services` repo.

