github-pr · git:20260303.a82a4eb · 2026-03-03 · sha256 c3a762fb56b8c24c

github-pr git:20260303.a82a4ebA

Immutable. This exact content is served forever at /api/v1/blob/c3a762fb56b8c24c.

---
name: github-pr
description: Create or update a GitHub pull request after committing, rebasing, and pushing changes. Use when the user asks to create a PR, submit changes for review, or open a pull request.
---

# GitHub Pull Request Workflow

## Setup: Detect Context

### 1. Authenticate

```bash
gh auth status
```

If not authenticated, tell user to run `gh auth login` and **stop**.

### 2. Detect Role

```bash
ORIGIN_URL=$(git remote get-url origin)
UPSTREAM_URL=$(git remote get-url upstream 2>/dev/null || echo "")
DEFAULT_BRANCH=$(git remote show origin | sed -n 's/.*HEAD branch: \(.*\)/\1/p')
```

Two developer roles use this repo differently:

| Role | `upstream` exists? | Push target | PR target |
| ---- | ------------------ | ----------- | --------- |
| **Repo owner** | No | `origin` | `origin/$DEFAULT_BRANCH` |
| **Fork contributor** | Yes | `origin` (fork) | `upstream/$DEFAULT_BRANCH` |

Compute these variables once — all later steps use them without role conditionals:

| Variable | Repo owner | Fork contributor |
| -------- | ---------- | ---------------- |
| `BASE_REF` | `origin/$DEFAULT_BRANCH` | `upstream/$DEFAULT_BRANCH` |
| `PUSH_REMOTE` | `origin` | `origin` |
| `PR_REPO` | *(omit — same repo)* | `OWNER/REPO` from `upstream` URL |
| `PR_HEAD` | `$BRANCH_NAME` | `FORK_OWNER:$BRANCH_NAME` (`FORK_OWNER` from `origin` URL) |

### 3. Fetch and Gather State

Single fetch for all remotes:

```bash
git fetch origin
if [ -n "$UPSTREAM_URL" ]; then git fetch upstream; fi
```

Gather current state:

```bash
BRANCH_NAME=$(git branch --show-current)
UNCOMMITTED=$(git status --porcelain)
COMMITS_AHEAD=$(git rev-list HEAD --not "$BASE_REF" --count)
```

### 4. Check for Existing PR

```bash
gh pr list --head "$BRANCH_NAME" --state open
```

### 5. Route

| Existing PR? | On default branch? | Commits ahead? | Uncommitted? | Route |
| ------------ | ------------------- | -------------- | ------------ | ----- |
| No | * | * | * | **Path A** (create new PR) |
| Yes | No | Ahead > 0 | * | **Path B** (update existing PR) |
| Yes | No | 0 | Yes | **Path B** (commit + update) |
| Yes | No | 0 | No | Already up to date — exit |

**Validation**: If no existing PR, `COMMITS_AHEAD == 0`, and no uncommitted changes — error. Nothing to PR.

---

## Path A: Create New PR

### A1. Prepare Branch

Check the current branch and its relationship to the changes being PR'd:

| Current branch | Commits ahead of `$BASE_REF`? | Commits related to current changes? | Action |
| -------------- | ----------------------------- | ----------------------------------- | ------ |
| `$DEFAULT_BRANCH` | — | — | Create new branch from HEAD |
| Feature branch | Yes, all related | Yes | Continue on it |
| Feature branch | Yes, unrelated commits exist | No | Stash → new branch from `$BASE_REF` → pop |
| Feature branch | No (0 ahead) | Branch name fits | Continue on it (uncommitted changes only) |
| Feature branch | No (0 ahead) | Branch name unrelated | Stash → new branch from `$BASE_REF` → pop |

**Case 1 — On `$DEFAULT_BRANCH`**: Create and switch to a new branch:

```bash
git checkout -b <branch-name>
```

**Case 2 — Feature branch, all commits related**: Continue on it. No action needed.

**Case 3 — Feature branch, unrelated commits exist**: The branch has commits that are not part of the changes you want to PR. Stash uncommitted work, create a new branch from `$BASE_REF`, and restore:

```bash
git stash
git checkout -b <branch-name> "$BASE_REF"
git stash pop
```

To detect this: inspect `git log $BASE_REF..HEAD --oneline` and compare those commits against the staged/unstaged changes. If any existing commits touch unrelated files or features, treat the branch as unrelated.

**Case 4 — Feature branch, 0 commits ahead, branch name fits**: Continue on it — only uncommitted changes will be committed.

**Case 5 — Feature branch, 0 commits ahead, branch name unrelated**: The branch name doesn't match the intent of the uncommitted changes. Stash, create a new branch, and restore:

```bash
git stash
git checkout -b <branch-name> "$BASE_REF"
git stash pop
```

See **[Branch Naming](#branch-naming)** for how to generate the name. Do NOT ask the user.

### A2. Commit Changes

If there are uncommitted changes, delegate to `/git-commit` (runs review and testing).

If already committed, skip.

### A3. Ensure Single Valid Commit

Follow the **[shared procedure](#shared-ensure-single-valid-commit)** below.

### A4. Rebase

```bash
git rebase "$BASE_REF"
```

On conflicts: resolve files, `git add`, `git rebase --continue`. If stuck: `git rebase --abort`.

After rebase, re-run the **[shared procedure](#shared-ensure-single-valid-commit)** if the rebase introduced changes.

### A5. Push

Always push to `origin`. Never push to `upstream` or other remotes. Use `git` commands (not `gh`).

```bash
git push --set-upstream origin "$BRANCH_NAME"
```

### A6. Create PR

```bash
gh pr create \
  --repo "$PR_REPO" \
  --base "$DEFAULT_BRANCH" \
  --head "$PR_HEAD" \
  --title "Brief description" \
  --body "$(cat <<'EOF'
## Summary
- Key change 1
- Key change 2

## Testing
- [ ] Simulation tests pass
- [ ] Hardware tests pass (if applicable)

Fixes #ISSUE_NUMBER (if applicable)
EOF
)"
```

Auto-generate title and body from the commit message. Keep title under 72 characters. Do NOT add AI co-author footers.

### Checklist A

- [ ] `gh auth status` passes
- [ ] Branch created (if on default branch)
- [ ] Changes committed via `/git-commit`
- [ ] Exactly 1 valid commit ahead of base
- [ ] Rebased onto `$BASE_REF`
- [ ] Pushed to `origin`
- [ ] PR created with clear title and summary

---

## Path B: Update Existing PR

Display the existing PR with `gh pr view`.

### B1. Commit Changes

If there are uncommitted changes, delegate to `/git-commit`.

If already committed, skip.

### B2. Ensure Single Valid Commit

Follow the **[shared procedure](#shared-ensure-single-valid-commit)** below.

### B3. Rebase

```bash
git rebase "$BASE_REF"
```

On conflicts: resolve files, `git add`, `git rebase --continue`. If stuck: `git rebase --abort`.

After rebase, re-run the **[shared procedure](#shared-ensure-single-valid-commit)** if the rebase introduced changes.

### B4. Push

```bash
git push --force-with-lease origin "$BRANCH_NAME"
```

Always push to `origin`. Never push to `upstream` or other remotes.

### B5. Update PR Title/Body

If the commit message changed, update the PR to match:

```bash
gh pr edit --title "Updated title" --body "Updated body"
```

### Checklist B

- [ ] `gh auth status` passes
- [ ] Changes committed via `/git-commit`
- [ ] Exactly 1 valid commit ahead of base
- [ ] Rebased onto `$BASE_REF`
- [ ] Force-pushed to `origin`
- [ ] PR title/body updated (if commit changed)

---

## Shared: Ensure Single Valid Commit

### Squash if Needed

```bash
COMMITS_AHEAD=$(git rev-list HEAD --not "$BASE_REF" --count)
```

If more than 1 commit ahead, squash:

```bash
git reset --soft "$BASE_REF"
git commit  # Re-commit with proper message via /git-commit conventions
```

### Validate Commit Message

```bash
git log -1 --format='%s'   # Subject line
git log -1 --format='%b'   # Body
```

| Rule | Check |
| ---- | ----- |
| Subject format | `Type: concise description` |
| Valid types | Add, Fix, Update, Refactor, Support, Sim, CI |
| Length | Under 72 characters |
| Style | Imperative mood, no trailing period |
| Body | Required if commit touches 3+ files |
| Co-author | No AI co-author lines |

If the message does not comply, amend:

```bash
git commit --amend -m "Type: corrected description"
```

For multi-line messages:

```bash
git commit --amend -m "$(cat <<'EOF'
Type: corrected description

- What changed and why
EOF
)"
```

---

## Post-Merge Cleanup (Repo Owner Only)

After the PR is merged:

```bash
git checkout "$DEFAULT_BRANCH"
git pull origin "$DEFAULT_BRANCH"
git branch -d "$BRANCH_NAME"
git push origin --delete "$BRANCH_NAME"
```

Fork contributors do not need remote cleanup.

---

## Reference

### Branch Naming

1. Determine the commit type prefix:

| Commit type | Branch prefix |
| ----------- | ------------- |
| Add | `feat/` |
| Fix | `fix/` |
| Update | `feat/` |
| Refactor | `refactor/` |
| Support | `support/` |
| Sim | `sim/` |
| CI | `support/` |

2. Take the commit subject description (after `Type: `), lowercase it, replace spaces and special characters with hyphens, strip trailing hyphens.

3. Truncate to 50 characters.

Example: `Refactor: inline ring buffer hot paths` → `refactor/inline-ring-buffer-hot-paths`

### Common Issues

| Issue | Solution |
| ----- | -------- |
| `gh auth` fails | Tell user to run `gh auth login` |
| PR already exists | Route to Path B |
| Merge conflicts during rebase | Resolve, `git add`, `git rebase --continue` |
| Push rejected (non-fast-forward) | `git push --force-with-lease` after rebase |
| More than 1 commit ahead | Squash via `git reset --soft` + re-commit |
| Can't detect role | Check `git remote -v` output |
| Nothing to PR | Error — tell user to make changes first |