| name | graphite | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| description | Work with Graphite (gt) for stacked PRs - creating, navigating, and managing PR stacks. | |||||||||||
| allowed-tools |
|
Work with Graphite (gt) for creating, navigating, and managing stacked pull requests.
Check for .git/.graphite_repo_config to determine if a repo uses Graphite:
- File exists: Use
gtcommands (this skill applies) - File does not exist: Use standard
gitcommands (this skill does NOT apply)
Every gt command must include --no-interactive. This is a global flag, not per-command. Without it, gt may open prompts, pagers, or editors that hang indefinitely in agent contexts. --force does NOT prevent prompts.
| I want to... | Command |
|---|---|
| Create a new branch/PR | gt create branch-name -m "message" --no-interactive |
| Amend current branch | gt modify -m "message" --no-interactive |
| Navigate up the stack | gt up --no-interactive |
| Navigate down the stack | gt down --no-interactive |
| Jump to top of stack | gt top --no-interactive |
| Jump to bottom of stack | gt bottom --no-interactive |
| View stack structure | gt ls --no-interactive |
| Submit stack for review | gt submit --no-interactive --ai --no-edit |
| Rebase stack on trunk | gt restack --no-interactive |
| Change branch parent | gt track --parent <branch> --no-interactive |
| Rename current branch | gt rename <new-name> --no-interactive |
| Move branch in stack | gt move --no-interactive |
| Pull someone's stack | gt get <branch-or-PR#> --no-interactive |
Each PR has real cost: CI runs, AI code review, merge queue, reviewer context-switching. Split when it helps reviewers reason about the change; don't split when it just multiplies overhead.
Split when:
- Changes are logically independent — different concerns, different reviewers, different risk profiles
- A large feature has natural seams (data model, API, UI) that are each meaningful on their own
Keep together when:
- A mechanical/sweeping change (formatter rule, rename, dependency bump) touches many files but is one logical operation — splitting it across PRs creates N reviews of the same trivial diff
- Splitting would produce PRs that are meaningless in isolation (e.g. "add unused import" as its own PR)
In all cases: each PR must be atomic (pass CI, safe to deploy independently) and have narrow semantic scope.
Branches follow {author}/{kebab-case-description}:
alice/fix-auth-token-refresh
bob/fonts-require-custom-fonts-feature
carol/m-337-homepage-prompt-box-ab-test
When a Linear ticket is provided, prefix the description with the lowercased ticket ID:
alice/inf-76-openapi-spec-ci-pipeline
bob/sec-115-hocuspocus-auth
For stacks, keep the author prefix and give each branch a distinct description — no nested slash grouping:
alice/auth-bugfix-reorder-args
alice/auth-bugfix-improve-logging
alice/auth-bugfix-handle-401
- Make changes to files
- Stage changes:
git add <files> - Create branch:
gt create branch-name -m "commit message" --no-interactive - Repeat for each PR in the stack
- Submit:
gt submit --no-interactive --ai --no-edit
Before creating branches, check if the current branch is tracked:
gt branch info --no-interactiveIf you see "ERROR: Cannot perform this operation on untracked branch":
Option A (Recommended): Track temporarily, then re-parent
- Track current branch:
gt track -p main --no-interactive - Create your stack normally with
gt create - After creating ALL branches, re-parent your first new branch onto main:
gt checkout <first-branch-of-your-stack> --no-interactive gt track -p main --no-interactive gt restack --no-interactive
Option B: Stash changes and start from main
git stashgit checkout main && git pull- Create your branch directly:
git checkout -b <branch-name> && git stash pop - Track it:
gt track -p main --no-interactive - Stage and create:
git add <files>thengt create <branch-name> -m "message" --no-interactive
gt up --no-interactive
gt down --no-interactive
gt top --no-interactive
gt bottom --no-interactive
gt ls --no-interactivegit add <files>
gt modify -m "message" --no-interactiveUse gt move --no-interactive to reorder branches in the stack. This is simpler than trying to use gt create --insert.
If you created a stack on top of a feature branch but want it based on main:
gt checkout <first-branch> --no-interactive
gt track --parent main --no-interactive
gt restack --no-interactivegt rename new-branch-name --no-interactiveIf changes are already committed but you want to re-stack them differently:
# Reset the last commit, keeping changes unstaged
git reset HEAD^
# Reset multiple commits (e.g., last 2 commits)
git reset HEAD~2
# View the diff to understand what you're working with
git diff HEADBefore running gt submit, verify the first PR is parented on main:
gt ls --no-interactiveIf the first branch has a parent other than main:
gt checkout <first-branch> --no-interactive
gt track -p main --no-interactive
gt restack --no-interactiveAfter creating each PR, run appropriate linting, building, and testing:
- Refer to the project's CLAUDE.md for specific commands
- If validation fails, fix the issue, stage changes, and use
gt modify --no-interactive
gt submit --no-interactive --ai --no-editUseful flags:
--reviewers alice,bob— request individual reviewers (comma-separated, not repeatable)--team-reviewers frontend,platform— request team reviewers (comma-separated, not repeatable)--draft— open PRs in draft mode
Do NOT enable auto-merge on your own. Never pass --merge-when-ready (or run gh pr merge --auto, gt merge, etc.) unless the user explicitly asks for it in this turn. Auto-merge ships code without further human review the moment checks pass — that's the user's call, not the agent's. A prior approval to auto-merge one PR does not carry over to later PRs.
When the user requests reviewers (e.g. "have the backend team review", "ask Alice to review"):
- Resolve to verified GitHub identifiers before submitting. Never pass unverified names.
- Individual:
gh api /repos/{owner}/{repo}/collaborators/{login} --silent(200 = has access) - Team:
gh api /orgs/{org}/teams/{slug} --silent(200 = exists)
- Individual:
- Disambiguate user vs. team. If unclear, check both — ask the user if it matches both. Never use
--reviewersfor a team slug or--team-reviewersfor a user login. - If a name doesn't resolve, stop and ask. Do not guess, do not attempt to grant access, do not invite collaborators.
After submitting, use gh pr edit to set proper titles and descriptions.
IMPORTANT: Never use Bash heredocs for PR descriptions - shell escaping breaks markdown tables, code blocks, etc. Instead:
- Use the
Writetool to create/tmp/pr-body.mdwith the full markdown content - Use
gh pr editwith--body-file:
gh pr edit <PR_NUMBER> --title "stack-name: description" --body-file /tmp/pr-body.mdPR descriptions must include:
- Stack Context: What is the bigger goal of this stack?
- What? (optional for small changes): Super terse, focus on what not why
- Why?: What prompted the change? Why this solution? How does it fit into the stack?
Example (for the first PR in a multi-PR stack):
## Stack Context
This stack adds <feature> by introducing <data model change>, exposing it through <API surface>,
and surfacing it in <UI>.
## Why?
<One or two sentences on the motivation — what user-facing problem or constraint prompted this.>
This PR is the foundation: it adds the <type/field/migration> that the later PRs in the stack
build on.Use gt get to pull a teammate's stack locally:
gt get their-branch-name --no-interactive
gt get 1234 --no-interactive # by PR numberFetched branches are frozen by default — you can read, review, and navigate them but local edits are blocked. This prevents accidental modifications to branches you don't own.
gt get their-branch --unfrozen --no-interactive # fetch editable from the start
gt unfreeze --no-interactive # unfreeze current branch after fetching
gt freeze --no-interactive # re-freeze when done editingWhen to freeze/unfreeze:
- Keep frozen when reviewing, testing, or rebasing your own work on top of their stack
- Unfreeze when the owner asks you to push fixes to their branch, or when pair-programming on a shared stack
| Problem | Solution |
|---|---|
| "Cannot perform this operation on untracked branch" | Run gt track -p main --no-interactive first |
| Stack parented on wrong branch | Use gt track -p main --no-interactive then gt restack --no-interactive |
| Need to reorder PRs | Use gt move --no-interactive |
| Conflicts during restack | Resolve conflicts, then gt continue -a --no-interactive |
| Want to split a PR | Reset commits (git reset HEAD^), re-stage selectively, create new branches |
| Need to delete a branch (non-interactive) | gt delete <branch> -f -q |
gt restack hitting unrelated conflicts |
Use targeted git rebase — see @.claude/skills/graphite/ADVANCED.md |
| Rebase interrupted mid-conflict | See recovery steps in @.claude/skills/graphite/ADVANCED.md |
When gt sync or gt restack hits conflicts:
- Understand what conflicted - check which branch and what files
- Check what each branch does - use
gt logand review the changes - Auto-resolve obvious conflicts:
- Import order changes
- Whitespace differences
- Non-overlapping additions
- Lock file conflicts: accept either version, regenerate (
yarn install), and stage
- Ask about ambiguous conflicts:
- Same code modified differently
- Deleted vs modified conflicts
- Semantic conflicts (logic changes)
- Test expectation changes
After resolving: gt continue -a --no-interactive. If stuck: gt abort --no-interactive.
Use gt parent — never parse gt log short output.
parent=$(gt parent --no-interactive)
git diff "$parent...HEAD"For surgical rebasing (when gt restack hits unrelated conflicts), branch deletion, corrupted metadata recovery, and interrupted rebase recovery, see @.claude/skills/graphite/ADVANCED.md.
For commands that produce verbose output (gt log, gt ls, gt diff, large git status), delegate to a subagent to avoid polluting the main conversation context. The main context should be reserved for judgment calls — conflict resolution, stack planning, PR descriptions — not command output.