Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"Skill(sentry-skills:skill-writer)",
"Skill(sentry-skills:sred-project-organizer)",
"Skill(sentry-skills:sred-work-summary)",
"Skill(sentry-skills:stacked-prs)",
"Skill(sentry-skills:triage-frontend-issues)",
"Skill(sentry-skills:typing-exclusion-worker)"
],
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Works with Claude Code, Cursor, Cline, GitHub Copilot, and other compatible agen
| [skill-writer](skills/skill-writer/SKILL.md) | Canonical workflow to synthesize, create, and iteratively improve agent skills for this repository. |
| [sred-project-organizer](skills/sred-project-organizer/SKILL.md) | Take a list of projects and their related documentation, and organize them into the SRED format for submission. |
| [sred-work-summary](skills/sred-work-summary/SKILL.md) | Go back through the previous year of work and create a Notion doc that groups relevant links into projects that can then be documented as SRED projects. |
| [stacked-prs](skills/stacked-prs/SKILL.md) | Safely update stacked PRs bottom-up without merging the default branch into a PR whose base is another feature branch. |
| [triage-frontend-issues](skills/triage-frontend-issues/SKILL.md) | Triage the Sentry `javascript` project queue by archiving non-actionable noise (third-party libs, browser quirks, transient 5xx, test traffic) with `untilEscalating` after user approval. |
| [typing-exclusion-worker](skills/typing-exclusion-worker/SKILL.md) | Python typing exclusion worker: remove assigned mypy exclusion modules in small scoped batches, fix typing issues, run validation, and produce a structured completion summary. |

Expand Down
1 change: 1 addition & 0 deletions skills/claude-settings-audit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ If this is a Sentry project (or sentry-skills plugin is installed), include:
"Skill(sentry-skills:skill-writer)",
"Skill(sentry-skills:sred-project-organizer)",
"Skill(sentry-skills:sred-work-summary)",
"Skill(sentry-skills:stacked-prs)",
"Skill(sentry-skills:triage-frontend-issues)",
"Skill(sentry-skills:typing-exclusion-worker)"
]
Expand Down
73 changes: 73 additions & 0 deletions skills/stacked-prs/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
name: stacked-prs
description: Safely update stacked pull requests whose base is another feature branch instead of the default branch. Use before merging or rebasing master/main into a PR branch, updating a PR stack, resolving conflicts on a stacked PR, or when a push balloons a PR's diff or requests review from many CODEOWNERS teams.
---

# Stacked PRs

GitHub diffs a PR against its **base** branch. If the base is another feature branch and you merge the default branch (`master`/`main`) into the PR branch, every default-branch commit the base doesn't have shows up in the PR. CODEOWNERS then requests review from every owning team on push, and those notifications can't be unsent.

**Rule:** never merge the default branch into a PR whose base isn't the default branch.

## Step 1: Check the base before any merge, rebase, or push

```bash
gh pr view <n> --json number,baseRefName,headRefName,changedFiles
gh repo view --json defaultBranchRef -q .defaultBranchRef.name
```

If `baseRefName` is the default branch, this skill doesn't apply; proceed normally.
Otherwise this PR is part of a stack. Find the PRs below it by following bases down until one targets the default branch:

```bash
gh pr list --head <baseRefName> --json number,baseRefName,headRefName,changedFiles
```

Record `changedFiles` for every PR in the stack before touching anything.

## Step 2: Update the stack bottom-up

```bash
git fetch origin
```

1. Bottom PR (base is the default branch): check out its branch, `git merge origin/<default>`, resolve conflicts, run Step 3, push.
2. Each PR above it, in order: check out its branch, `git merge origin/<its base>` (the branch you just pushed), resolve conflicts, run Step 3, push.

"Conflicts" on an upper PR are usually the bottom PR's conflicts with the default branch. Resolve them in the bottom PR first, then let them flow up through the merges.

Never merge `origin/<default>` directly into an upper PR. If the user asked to "merge master into" an upper PR, do the bottom-up update instead and say so.

## Step 3: Verify the diff before every push

```bash
git fetch origin <base>
git diff --name-only origin/<base>...HEAD | wc -l
```

Compare this with the `changedFiles` recorded in Step 1.

- Same or close (the change itself added or removed a few files): push.
- Count jumps: **stop. Don't push.** Tell the user the expected and actual counts and ask how to proceed. The usual cause is a default-branch merge into an upper PR or a base that hasn't been updated and pushed yet.

## Recovery: a bad push already went out

Notifications already sent can't be recalled, but remove the extra review requests so the teams aren't left blocking the PR.

1. Fix the branch first (redo Step 2 correctly) so the diff is back to the expected size; otherwise a later push re-requests the same teams.
2. List review-request events with timestamps and find the ones added at the bad push:

```bash
gh api --paginate repos/<owner>/<repo>/issues/<n>/timeline \
--jq '.[] | select(.event == "review_requested") | [.created_at, (.requested_team.slug // .requested_reviewer.login)] | @tsv'
```

3. Keep requests that existed before the bad push. Remove only the ones it added:

```bash
gh api -X DELETE repos/<owner>/<repo>/pulls/<n>/requested_reviewers \
-f 'team_reviewers[]=<team-slug>' -f 'team_reviewers[]=<team-slug>' \
-f 'reviewers[]=<login>'
```

4. Confirm with `gh pr view <n> --json reviewRequests` and report which requests were removed.
70 changes: 70 additions & 0 deletions skills/stacked-prs/SPEC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Stacked PRs Specification

## Intent

Prevent agents from merging the default branch into a stacked PR (one whose base is another feature branch). That mistake floods the PR diff with unrelated commits and makes CODEOWNERS request review from dozens of teams, which can't be unsent.

This lives as an on-demand skill instead of always-on `CLAUDE.md`/`AGENTS.md` context so it only costs tokens when an agent is actually updating a PR branch.

## Scope

In scope:
- Detecting whether a PR is part of a stack before merge, rebase, or push.
- Updating a stack bottom-up from the default branch.
- Pre-push diff-size check against the PR's recorded `changedFiles`.
- Cleaning up review requests added by a bad push.

Out of scope:
- Creating stacks or choosing a stacking tool (Graphite, ghstack, git-town, etc.).
- Rebase-based stack workflows; the runtime flow assumes merge-based updates.
- Iterating on CI or review feedback (use `iterate-pr`).

## Users And Trigger Context

- Primary users: engineers and coding agents updating existing PR branches.
- Invocation: model-invocable, so it triggers before an agent merges or rebases the default branch into a PR branch.
- Common user requests: merge master into this PR, update my stack, fix conflicts on this stacked PR, why did my PR request review from every team.
- Should not trigger for: PRs that target the default branch, creating new PRs, writing PR descriptions.

## Runtime Contract

- Required first actions: read the PR's `baseRefName` and `changedFiles`, and the repo's default branch.
- Required outputs: stack updated bottom-up, or a stop-and-ask report when the diff size jumps.
- Non-negotiable constraints: never merge the default branch into a non-bottom stacked PR; never push when the file count jumps without user confirmation; only remove review requests the bad push added.
- Expected bundled files loaded at runtime: `SKILL.md` only.

## Source And Evidence Model

Authoritative sources:
- GitHub PR comparison semantics (diff against base) and CODEOWNERS auto-request behavior.
- GitHub REST endpoints: issue timeline (`review_requested` events) and `DELETE /pulls/{n}/requested_reviewers`.
- Original guidance: an engineer's `CLAUDE.md` rule written after an agent merged master into a stacked PR in `getsentry/sentry` and requested review from ~20 CODEOWNERS teams.

Useful improvement sources:
- negative examples: pushes that triggered mass CODEOWNERS requests.
- positive examples: stacks updated bottom-up without diff growth.

Data that must not be stored:
- secrets
- customer data
- private URLs or identifiers not needed for reproduction

## Reference Architecture

- `SKILL.md` contains the full runtime workflow.
- `references/`, `scripts/`, `assets/`: none.

## Validation

- Lightweight validation: `uv run skills/skill-writer/scripts/quick_validate.py skills/stacked-prs`.
- Acceptance gates: validator passes; workflow refuses a direct default-branch merge into an upper stacked PR; pre-push check stops on a file-count jump.

## Known Limitations

- `changedFiles` comparison is a heuristic; legitimate large changes can trip it, which is why it asks instead of failing.
- Stack discovery via `gh pr list --head` assumes one open PR per head branch.

## Maintenance Notes

- Update `SKILL.md` if GitHub changes timeline event shapes or the requested-reviewers API.
- Revisit whether the skill is still needed as models stop making this mistake unprompted.
Loading