Skip to content

Instantly share code, notes, and snippets.

@abahgat
Created January 26, 2026 19:57
Show Gist options
  • Select an option

  • Save abahgat/756ca99951dba15fb2d09344bc599300 to your computer and use it in GitHub Desktop.

Select an option

Save abahgat/756ca99951dba15fb2d09344bc599300 to your computer and use it in GitHub Desktop.
Stacked Linear PRs skill for Claude Code
#!/usr/bin/env python3
"""
Dependency Graph Analyzer for Linear Issues
This script helps visualize and validate dependency relationships between Linear issues
for stacked PR workflows.
Usage:
python dependency_graph.py --help
python dependency_graph.py --visualize <issue_data.json>
python dependency_graph.py --check-cycles <issue_data.json>
python dependency_graph.py --work-order <issue_data.json>
"""
import json
import sys
import argparse
from collections import defaultdict, deque
from typing import Dict, List, Set, Tuple, Optional
class Issue:
"""Represents a Linear issue with its dependencies"""
def __init__(self, data: dict):
self.id = data.get("id")
self.identifier = data.get("identifier", "UNKNOWN")
self.title = data.get("title", "Untitled")
self.priority = data.get("priority", 0)
self.parent_id = (
data.get("parent", {}).get("id") if data.get("parent") else None
)
# Extract relationships
relations = data.get("relations", {})
self.blocked_by = [r.get("id") for r in relations.get("blockedByIssues", [])]
self.blocks = [r.get("id") for r in relations.get("blockingIssues", [])]
self.related = [r.get("id") for r in relations.get("relatedIssues", [])]
def __repr__(self):
return f"Issue({self.identifier}: {self.title})"
class DependencyGraph:
"""Builds and analyzes dependency graphs for Linear issues"""
def __init__(self, issues: List[Issue]):
self.issues = {issue.id: issue for issue in issues}
self.graph = self._build_graph()
def _build_graph(self) -> Dict[str, Dict]:
"""Build adjacency list representation of dependency graph"""
graph = {}
# Initialize nodes
for issue_id, issue in self.issues.items():
graph[issue_id] = {
"issue": issue,
"dependencies": set(), # Issues this depends on (must complete first)
"dependents": set(), # Issues that depend on this
}
# Add blocked-by relationships
for issue_id, issue in self.issues.items():
for blocker_id in issue.blocked_by:
if blocker_id in graph:
graph[issue_id]["dependencies"].add(blocker_id)
graph[blocker_id]["dependents"].add(issue_id)
# Add parent-child relationships (if not already covered)
for issue_id, issue in self.issues.items():
if issue.parent_id and issue.parent_id in graph:
# Check if parent blocks child or child blocks parent
parent_blocks_child = issue.parent_id in issue.blocked_by
child_blocks_parent = (
issue_id in self.issues[issue.parent_id].blocked_by
)
if not parent_blocks_child and not child_blocks_parent:
# Default: parent comes before child
graph[issue_id]["dependencies"].add(issue.parent_id)
graph[issue.parent_id]["dependents"].add(issue_id)
return graph
def topological_sort(self) -> Tuple[List[str], bool]:
"""
Perform topological sort to determine valid work order.
Returns:
(ordered_ids, success): List of issue IDs in work order, and whether sort succeeded
"""
in_degree = {node: len(self.graph[node]["dependencies"]) for node in self.graph}
queue = deque([node for node, degree in in_degree.items() if degree == 0])
result = []
while queue:
# Sort by priority (higher priority first) for stable ordering
queue = deque(sorted(queue, key=lambda n: -self.graph[n]["issue"].priority))
node = queue.popleft()
result.append(node)
# Reduce in-degree for dependents
for dependent in self.graph[node]["dependents"]:
in_degree[dependent] -= 1
if in_degree[dependent] == 0:
queue.append(dependent)
# Check if all nodes were visited (no cycles)
success = len(result) == len(self.graph)
return result, success
def find_cycles(self) -> List[List[str]]:
"""
Detect cycles in the dependency graph using DFS.
Returns:
List of cycles, where each cycle is a list of issue IDs
"""
visited = set()
rec_stack = set()
cycles = []
def dfs(node, path):
visited.add(node)
rec_stack.add(node)
path.append(node)
for dependent in self.graph[node]["dependents"]:
if dependent not in visited:
dfs(dependent, path[:])
elif dependent in rec_stack:
# Found a cycle
cycle_start = path.index(dependent)
cycle = path[cycle_start:] + [dependent]
cycles.append(cycle)
rec_stack.remove(node)
for node in self.graph:
if node not in visited:
dfs(node, [])
return cycles
def get_roots(self) -> List[str]:
"""Get issues with no dependencies (can start immediately)"""
return [
issue_id
for issue_id, data in self.graph.items()
if len(data["dependencies"]) == 0
]
def get_leaves(self) -> List[str]:
"""Get issues with no dependents (end of chains)"""
return [
issue_id
for issue_id, data in self.graph.items()
if len(data["dependents"]) == 0
]
def visualize_ascii(self) -> str:
"""Generate ASCII art visualization of the dependency graph"""
order, success = self.topological_sort()
if not success:
cycles = self.find_cycles()
output = ["⚠️ DEPENDENCY CYCLE DETECTED!", ""]
for cycle in cycles:
cycle_strs = [self.issues[id].identifier for id in cycle]
output.append(f"Cycle: {' β†’ '.join(cycle_strs)}")
output.append("")
return "\n".join(output)
output = ["Dependency Graph (Work Order):", ""]
for i, issue_id in enumerate(order, 1):
issue = self.graph[issue_id]["issue"]
deps = self.graph[issue_id]["dependencies"]
prefix = f"{i}. "
output.append(f"{prefix}{issue.identifier} - {issue.title}")
if deps:
dep_strs = [self.issues[d].identifier for d in deps]
output.append(f" └─ Depends on: {', '.join(dep_strs)}")
output.append("")
return "\n".join(output)
def to_mermaid(self) -> str:
"""Generate Mermaid diagram syntax for the dependency graph"""
lines = ["graph TD"]
# Add nodes
for issue_id, issue in self.issues.items():
label = f"{issue.identifier}: {issue.title[:30]}"
lines.append(f' {issue_id}["{label}"]')
# Add edges
for issue_id, data in self.graph.items():
for dep_id in data["dependencies"]:
lines.append(f" {dep_id} --> {issue_id}")
return "\n".join(lines)
def get_statistics(self) -> Dict:
"""Get statistics about the dependency graph"""
order, has_cycle = self.topological_sort()
return {
"total_issues": len(self.issues),
"has_cycle": not has_cycle,
"num_roots": len(self.get_roots()),
"num_leaves": len(self.get_leaves()),
"max_depth": self._calculate_max_depth(),
"avg_dependencies": sum(len(d["dependencies"]) for d in self.graph.values())
/ len(self.graph),
}
def _calculate_max_depth(self) -> int:
"""Calculate the maximum depth of the dependency tree"""
def dfs_depth(node, visited):
if node in visited:
return 0
visited.add(node)
max_child_depth = 0
for dependent in self.graph[node]["dependents"]:
max_child_depth = max(max_child_depth, dfs_depth(dependent, visited))
return 1 + max_child_depth
roots = self.get_roots()
return max(dfs_depth(root, set()) for root in roots) if roots else 0
def load_issues_from_json(filepath: str) -> List[Issue]:
"""Load issues from a JSON file"""
with open(filepath, "r") as f:
data = json.load(f)
# Support both single issue and array of issues
if isinstance(data, list):
return [Issue(item) for item in data]
else:
return [Issue(data)]
def main():
parser = argparse.ArgumentParser(
description="Analyze dependency graph for Linear issues",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("input_file", help="JSON file containing Linear issue data")
parser.add_argument(
"--visualize",
"-v",
action="store_true",
help="Display ASCII visualization of the graph",
)
parser.add_argument(
"--mermaid", "-m", action="store_true", help="Output Mermaid diagram syntax"
)
parser.add_argument(
"--check-cycles", "-c", action="store_true", help="Check for dependency cycles"
)
parser.add_argument(
"--work-order",
"-w",
action="store_true",
help="Output work order (topological sort)",
)
parser.add_argument(
"--stats", "-s", action="store_true", help="Display graph statistics"
)
args = parser.parse_args()
# Load issues
try:
issues = load_issues_from_json(args.input_file)
except FileNotFoundError:
print(f"Error: File '{args.input_file}' not found", file=sys.stderr)
return 1
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in '{args.input_file}': {e}", file=sys.stderr)
return 1
if not issues:
print("Error: No issues found in input file", file=sys.stderr)
return 1
# Build graph
graph = DependencyGraph(issues)
# If no flags specified, show visualization by default
if not any(
[args.visualize, args.mermaid, args.check_cycles, args.work_order, args.stats]
):
args.visualize = True
# Execute requested operations
if args.visualize:
print(graph.visualize_ascii())
if args.mermaid:
print(graph.to_mermaid())
if args.check_cycles:
cycles = graph.find_cycles()
if cycles:
print("⚠️ Dependency cycles detected!")
for i, cycle in enumerate(cycles, 1):
cycle_strs = [graph.issues[id].identifier for id in cycle]
print(f"\nCycle {i}: {' β†’ '.join(cycle_strs)}")
return 1
else:
print("βœ“ No dependency cycles detected")
if args.work_order:
order, success = graph.topological_sort()
if success:
print("Work Order:")
for i, issue_id in enumerate(order, 1):
issue = graph.issues[issue_id]
print(f"{i}. {issue.identifier} - {issue.title}")
else:
print(
"Error: Cannot determine work order (dependency cycle exists)",
file=sys.stderr,
)
return 1
if args.stats:
stats = graph.get_statistics()
print("Graph Statistics:")
print(f" Total issues: {stats['total_issues']}")
print(f" Has cycles: {stats['has_cycle']}")
print(f" Root issues (no dependencies): {stats['num_roots']}")
print(f" Leaf issues (no dependents): {stats['num_leaves']}")
print(f" Maximum depth: {stats['max_depth']}")
print(f" Average dependencies per issue: {stats['avg_dependencies']:.2f}")
return 0
if __name__ == "__main__":
sys.exit(main())

Stacked Linear PRs - Reference Documentation

Dependency Graph Algorithm

Graph Construction

Nodes: Linear issues Edges: Dependencies between issues

Edge types:

  1. Blocked-by edge: If issue A has issue B in blockedByIssues, create edge B β†’ A (B must complete before A)
  2. Parent-child edge: If issue C is a child of P, create edge P β†’ C (parent typically comes first, unless child blocks parent)

Algorithm:

def build_dependency_graph(parent_issue, child_issues):
    graph = {}
    all_issues = [parent_issue] + child_issues

    # Initialize graph nodes
    for issue in all_issues:
        graph[issue.id] = {
            'issue': issue,
            'dependencies': [],  # Issues this depends on (must complete first)
            'dependents': []     # Issues that depend on this
        }

    # Add blocked-by relationships
    for issue in all_issues:
        for blocker in issue.relations.blockedByIssues:
            if blocker.id in graph:
                graph[issue.id]['dependencies'].append(blocker.id)
                graph[blocker.id]['dependents'].append(issue.id)

    # Add parent-child relationships (if not already covered by blocked-by)
    for child in child_issues:
        if parent_issue.id not in graph[child.id]['dependencies']:
            # Check if child blocks parent
            if parent_issue.id not in [b.id for b in child.relations.blockingIssues]:
                # Parent should come before child
                graph[child.id]['dependencies'].append(parent_issue.id)
                graph[parent_issue.id]['dependents'].append(child.id)

    return graph

Topological Sort

Purpose: Determine valid work order where all dependencies are satisfied.

Algorithm:

def topological_sort(graph):
    # Calculate in-degree (number of dependencies)
    in_degree = {node: len(graph[node]['dependencies']) for node in graph}

    # Queue of nodes with no dependencies
    queue = [node for node, degree in in_degree.items() if degree == 0]
    result = []

    while queue:
        # Sort by priority (prefer higher priority issues)
        queue.sort(key=lambda n: graph[n]['issue'].priority)

        node = queue.pop(0)
        result.append(node)

        # Reduce in-degree for dependents
        for dependent in graph[node]['dependents']:
            in_degree[dependent] -= 1
            if in_degree[dependent] == 0:
                queue.append(dependent)

    # Check for cycles
    if len(result) != len(graph):
        remaining = set(graph.keys()) - set(result)
        raise Exception(f"Dependency cycle detected involving: {remaining}")

    return result

Cycle Detection

If topological sort fails to include all nodes, there's a cycle.

Handling cycles:

  1. Identify the cycle using DFS
  2. Report to user: "Issues X, Y, Z form a dependency cycle"
  3. Ask user to clarify the correct order or remove blocking relationships
  4. Do not proceed until cycle is resolved

Stacked PR Workflow Patterns

PR Metadata Tracking

Track these fields for each PR in the stack:

pr_stack = [
    {
        'issue_id': 'abc-123',
        'identifier': 'QUI-2982',
        'branch': 'qui-2982-feature-a',
        'pr_number': 123,
        'base': 'main',
        'position': 1
    },
    {
        'issue_id': 'def-456',
        'identifier': 'QUI-2983',
        'branch': 'qui-2983-feature-b',
        'pr_number': 124,
        'base': 'qui-2982-feature-a',
        'position': 2
    }
]

PR Body Template

## Summary
{brief_description}

## Related Issue
Addresses {issue_identifier} ({issue_url})

## Stack Context
This PR is part of a stacked PR workflow:
- **Stack position:** {position}/{total_prs}
- **Base:** {base_pr_link or "main"}
- **Next in stack:** {next_pr_link or "none (top of stack)"}

## Stack Overview
{list_all_prs_in_stack}

## Changes
{detailed_changes}

## Test Plan
{test_plan}

## Dependencies
{list_of_blocking_issues_if_any}

Rebase Workflow After Bottom PR Merges

When the bottom PR in the stack merges to main:

# 1. Update local main
git checkout main
git pull origin main

# 2. Update next PR's base to main
gh pr edit <next-pr-number> --base main

# 3. Rebase next branch onto main
git checkout <next-branch>
git rebase main
git push --force-with-lease origin <next-branch>

# 4. Repeat for remaining PRs in stack
# - Next PR now targets main
# - Following PRs still target their immediate predecessor
# - Continue this process as each PR merges

Merge Conflict Resolution

If rebase encounters conflicts:

  1. Identify conflicting files
  2. Resolve conflicts
  3. Continue rebase: git rebase --continue
  4. Force push: git push --force-with-lease
  5. Verify PR builds and tests pass

Prevention:

  • Keep PRs small (< 250 lines)
  • Merge PRs quickly once approved
  • Communicate about overlapping work

Best Practices

PR Size Guidelines

  • Ideal: < 250 lines of code changed
  • Maximum: < 500 lines of code changed
  • If larger, break into more issues

Review Process

For reviewers:

  • Review from bottom of stack upward
  • Each PR should be understandable independently
  • Approve PRs when ready (don't wait for entire stack)

For authors:

  • Keep each PR focused on one concern
  • Write clear PR descriptions
  • Update descriptions if implementation changes
  • Use "merge when ready" if available

Stack Maintenance During Review

  • Avoid force-pushing unless necessary
  • Update code in the relevant PR's branch
  • Changes to lower PRs may require rebasing upper PRs
  • Document blockers or dependencies in PR descriptions

Troubleshooting

Branch Name Missing from Linear

Symptom: branchName field is null or empty

Solution: Generate using pattern:

def generate_branch_name(issue):
    slug = issue.title.lower()
    slug = re.sub(r'[^a-z0-9]+', '-', slug)[:50]
    return f"{issue.team.key}-{issue.number}-{slug}"

Dependency Cycle Detected

Symptom: Topological sort fails

Solution:

  1. Use DFS to find the cycle
  2. Report cycle to user
  3. Ask user to resolve by removing or clarifying relationships
  4. Don't proceed until resolved

PR Creation Fails

Common causes:

  • Not authenticated: Run gh auth login
  • Branch not pushed: Run git push -u origin <branch>
  • Base branch doesn't exist: Verify previous PR's branch

Can't Determine Work Order

Symptom: Multiple issues with no explicit dependencies

Solution:

  1. Show issues to user
  2. Ask for preferred order
  3. Suggest order based on:
    • Issue priority
    • Creation date
    • Logical relationships in descriptions

Useful Commands

Visualize dependency graph

python scripts/dependency_graph.py issues.json --visualize

Check git state

git log --oneline --graph --all

List PRs with their bases

gh pr list --json number,title,baseRefName,headRefName

Examples

See WORKFLOW.md for detailed workflow examples.

name description allowed-tools
stacked-linear-prs
Creates and maintains stacked PRs from a set of related Linear issues, respecting dependencies and blocked-by relationships. Use when the user mentions stacked PRs, working on related Linear issues in sequence, wants to tackle a parent issue with child issues using a stacked workflow, references Linear issue identifiers (like QUI-2982) with mentions of dependencies or blocked-by relationships, or needs to rebase/update an existing stack of PRs.
mcp__linear__get_issue
mcp__linear__list_issues
mcp__linear__list_comments
Bash
Read
Glob
Grep
AskUserQuestion
TodoWrite
EnterPlanMode

Stacked Linear PRs

Implements a workflow for tackling related Linear issues as stacked pull requests, where each issue becomes a PR that builds upon the previous one, respecting explicit dependencies.

Critical Rule: Always Fetch Before Rebasing

ALWAYS fetch the latest upstream before any rebase operation:

git fetch origin main

Failure to do this causes the most common stacked PR problem: including commits from an already-merged PR because your local main is stale.

Key Concepts

A stacked PR structure looks like:

main
  └── PR1: Feature flag (base)
       β”œβ”€β”€ PR2: Device implementation β†’ depends on PR1
       β”‚    └── PR3: Device tests β†’ depends on PR2
       └── PR4: Mobile implementation β†’ depends on PR1
            └── PR5: Mobile UI β†’ depends on PR4

Workflow Overview

Progress Checklist:
- [ ] Step 1: Fetch and analyze issues
- [ ] Step 2: Build dependency graph
- [ ] Step 3: Determine work order
- [ ] Step 3.5: Decide planning approach (EnterPlanMode if needed)
- [ ] Step 4: Execute work plan (for each issue: branch, implement, commit, PR, verify checks)
- [ ] Step 5: Verify stack integrity

Step 1: Fetch and Analyze Issues

Fetch parent issue: Use mcp__linear__get_issue with includeRelations: true to fetch the parent issue including blocked-by relationships.

Fetch child issues: Use mcp__linear__list_issues with parentId filter to get all child issues.

Read issue comments: For each issue, use mcp__linear__list_comments to fetch comments. Comments may contain additional context, clarifications, or dependencies not captured in the issue body.

Extract key information:

  • Issue ID and identifier (e.g., QUI-2982)
  • Title and description
  • Comments (important context and dependencies)
  • Branch name from branchName field
  • Blocked-by relationships from relations.blockedByIssues
  • Child issues

Step 2: Build Dependency Graph

Create a dependency map considering:

  1. Explicit dependencies: blockedByIssues - if issue A is blocked by issue B, B must be completed first
  2. Parent-child relationships: Children typically depend on parent (unless they block the parent)
  3. Logical dependencies: Issues that reference each other in comments or descriptions

Use scripts/dependency_graph.py to visualize and validate the graph if needed.

See references/REFERENCE.md for detailed dependency graph algorithms.

Step 3: Determine Work Order

Perform a topological sort on the dependency graph:

  1. Issues with no dependencies come first
  2. Issues that are blocked come after their blocking issues
  3. If no explicit dependencies exist among children, use creation order or priority

Important: Show the user the work order, then decide whether to use Plan Mode or proceed directly (see below).

Step 3.5: Decide Planning Approach

Use EnterPlanMode (detailed planning) when:

  • Complex dependencies: 5+ issues, cycles detected, or cross-dependencies
  • Unclear implementation: Issues lack detailed descriptions or acceptance criteria
  • Inconsistent information: Issues may conflict with each other or codebase
  • Codebase exploration needed: Unclear how changes fit with existing patterns
  • User uncertainty: Multiple valid approaches exist

Proceed directly (with confirmation) when:

  • Simple linear stack: 3-4 issues with clear sequential dependencies
  • Well-defined issues: Detailed descriptions, acceptance criteria, implementation notes
  • Consistent information: Issues align with each other and existing codebase patterns
  • Clear approach: Implementation path is obvious from issue descriptions
  • User has provided specific guidance on how to proceed

In Plan Mode, you'll:

  1. Explore the codebase to understand current patterns
  2. Design the implementation approach for each issue
  3. Present the complete plan to user for approval
  4. Use AskUserQuestion to clarify ambiguities
  5. Exit plan mode and proceed to Step 4 after approval

Step 4: Execute Work Plan

For each issue in the determined order:

4a. Create or switch to branch

For the first issue (bottom of stack):

git checkout main
git pull origin main
git checkout -b <linear-branch-name>

For subsequent issues (stacking):

# Stay on current branch (which has previous issue's work)
git checkout -b <next-issue-branch-name>

4b. Work on the issue

  • Read the issue description, comments, and requirements
  • Implement the changes needed
  • Follow user's guidance if they want to be involved
  • Test the changes

4c. Commit changes

git add .
git commit -m "<commit-message>

Addresses <issue-identifier>

πŸ€– Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"

Commit message format:

  • First line: Brief summary (e.g., "feat: add login form UI")
  • Blank line
  • "Addresses "
  • Blank line
  • Claude Code attribution

4d. Create pull request

For the first PR (bottom of stack):

git push -u origin <branch-name>
gh pr create --draft --title "<issue-title>" --body "<pr-body>" --base main

For subsequent PRs (stacking):

git push -u origin <branch-name>
gh pr create --draft --title "<issue-title>" --body "<pr-body>" --base <previous-branch-name>

Note: PRs are created as drafts by default to signal work-in-progress while building the stack. Mark as ready for review once the entire stack is complete or when individual PRs are ready.

PR body template:

## Summary
<Brief description of changes>

## Related Issue
Addresses <issue-identifier> (<issue-url>)

## Stack Context
This PR is part of a stacked PR workflow:
- Base: <previous-pr-url or "main">
- Next: <next-pr-url or "none (top of stack)">

## Test Plan
<Testing approach>

4e. Verify PR checks

Before moving to the next issue, ensure PR checks pass:

gh pr checks <pr-number> --watch

Important:

  • Wait for all CI/CD checks to complete and pass
  • If checks fail, fix issues before proceeding to next issue
  • Each layer of the stack should be solid before building on top
  • Failing checks in lower PRs will propagate issues to dependent PRs

If checks fail, address the issues, commit fixes, push, and wait for checks to pass again.

Step 5: Verify Stack Integrity

After all PRs are created:

  1. Check that each PR (except the first) targets the previous PR's branch
  2. Verify the PR chain is correct
  3. List all PRs with their relationships
  4. Provide the user with a summary and next steps

Important Notes

Branch naming: Always use the branchName from Linear's issue data. If missing, generate using pattern: <team-key>-<issue-number>-<slug>

Merge strategy: When a PR merges, remaining PRs need rebasing. After the bottom PR merges to main, update the next PR to target main. Continue this process up the stack.

Rebasing an Existing Stack

When PRs in a stack need to be updated (e.g., after a PR merges, or to incorporate main branch changes), follow this workflow carefully to avoid duplicate commits or merge conflicts.

Rebase Checklist

- [ ] Step R1: Fetch latest upstream and check merge status
- [ ] Step R2: Build the current stack state
- [ ] Step R3: Rebase in correct order (bottom to top)
- [ ] Step R4: Verify each PR after rebasing
- [ ] Step R5: Force push updated branches

Step R1: Fetch Latest Upstream and Check Merge Status

CRITICAL: Always fetch the latest main before any rebase operation:

git fetch origin main

Check which PRs in the stack have merged:

# For each PR in the stack
gh pr view <pr-number> --json state,mergedAt

If a PR has merged, its commits are now in main. Dependent PRs should be rebased onto main (not the old branch), and git will automatically skip the duplicate commits.

Step R2: Build the Current Stack State

List all PRs in the stack and their current base branches:

gh pr list --author <username> --state open --json number,title,headRefName,baseRefName

Identify:

  • Which PRs target main
  • Which PRs target other branches in the stack
  • Which base branches have been merged (their PRs should now target main)

Step R3: Rebase in Correct Order

Order matters! Always rebase from the bottom of the stack upward:

  1. PRs targeting main first
  2. Then PRs that depend on those
  3. Continue up the stack

For a PR whose base has merged into main:

git checkout <branch-name>
git rebase origin/main
# Git will automatically skip commits already in main ("patch contents already upstream")
git push --force-with-lease
# Update PR base branch if needed
gh pr edit <pr-number> --base main

For a PR whose base has NOT merged:

git checkout <branch-name>
git rebase <base-branch-name>
git push --force-with-lease

Step R4: Verify Each PR After Rebasing

After rebasing each PR, verify it contains only the expected changes:

gh pr diff <pr-number> --name-only

Red flags to check for:

  • Files that belong to a different PR in the stack (indicates duplicate commits weren't skipped)
  • Unexpected files from other features
  • Missing expected files

If a PR contains unexpected files, the rebase was done against a stale base. Re-fetch and rebase again:

git fetch origin main
git rebase origin/main  # or the correct base branch

Step R5: Force Push and Update PR Bases

After successful rebase:

git push --force-with-lease

If a PR's base branch has merged, update the PR to target main:

gh pr edit <pr-number> --base main

Common Rebase Issues

Issue: PR contains commits from another PR in the stack

Cause: Rebased onto a stale version of main that didn't include the merged PR.

Fix:

git fetch origin main
git rebase origin/main  # Will skip duplicate commits
git push --force-with-lease

Issue: Merge conflicts during rebase

If conflicts occur:

  1. Resolve conflicts in the affected files
  2. git add <resolved-files>
  3. git rebase --continue
  4. If a commit was just for resolving previous rebase conflicts, consider git rebase --skip

Issue: "patch contents already upstream" messages

This is normal and expected! Git detected that a commit's changes are already in the base branch and skipped it. This happens when:

  • A dependent PR merged and its commits are now in main
  • Commits were cherry-picked between branches

Issue: PR base branch doesn't exist

Cause: Base PR was merged and branch was deleted.

Solution: The PR was automatically retargeted to main. Just rebase onto main:

git fetch origin main
git rebase origin/main
git push --force-with-lease

Checking Stack Health

Verify all PRs in your stack show only their own changes:

# Check each PR's files
gh pr diff 7868 --name-only  # Should show only PR1 files
gh pr diff 7869 --name-only  # Should show only PR2 files (not PR1)
gh pr diff 7870 --name-only  # Should show only PR3 files (not PR1 or PR2)

If a downstream PR shows files from an upstream PR, it needs rebasing.

Automation Checklist

When asked to rebase a stack, follow this checklist:

  1. git fetch origin main - Always first!
  2. Identify which PRs in the stack are merged
  3. List remaining PRs in dependency order
  4. Rebase each PR onto its new base (main or parent branch)
  5. Push each rebased branch with --force-with-lease
  6. Verify each PR with gh pr diff <num> --name-only

Example: Full Stack Rebase

Given this stack where PR1 just merged:

main ← PR1 (merged) ← PR2 ← PR3
                    β†– PR4 ← PR5

Execute:

# Always fetch first!
git fetch origin main

# Rebase PR2 (was based on PR1, now main)
git checkout pr2-branch && git rebase origin/main && git push --force-with-lease

# Rebase PR3 (based on PR2)
git checkout pr3-branch && git rebase pr2-branch && git push --force-with-lease

# Rebase PR4 (was based on PR1, now main)
git checkout pr4-branch && git rebase origin/main && git push --force-with-lease

# Rebase PR5 (based on PR4)
git checkout pr5-branch && git rebase pr4-branch && git push --force-with-lease

# Verify all PRs
gh pr diff <pr2-num> --name-only
gh pr diff <pr3-num> --name-only
gh pr diff <pr4-num> --name-only
gh pr diff <pr5-num> --name-only

Review order: Reviewers should review from bottom to top (closest to main first). Each PR should be reviewable independently.

Error handling:

  • If dependency cycle detected, alert user and ask for clarification
  • If PR creation fails, stop and report error before proceeding
  • Use scripts/dependency_graph.py to debug complex dependency issues

Advanced Options

Partial stack: Work on subset of issues - analyze all, then specify which to include/exclude

Custom branch names: Specify upfront if preferring custom names over Linear's defaults

Dry run: Request to see the plan without executing changes

Related Documentation

Stacked Linear PRs - Workflow Examples

This document provides detailed examples of using the stacked Linear PRs skill in various scenarios.

Table of Contents

  1. Simple Linear Workflow
  2. Complex Dependency Graph
  3. Handling Blocked-By Relationships
  4. Working with Existing Branches
  5. Recovering from Conflicts
  6. Partial Stack Execution

Simple Linear Workflow

Scenario: Parent issue with 3 child issues, no explicit dependencies, tackle in creation order.

Setup

Linear Issues:

  • QUI-2982: Implement user authentication (Parent)
    • QUI-2983: Add login form UI (Child)
    • QUI-2984: Implement JWT token service (Child)
    • QUI-2985: Add logout functionality (Child)

No blocked-by relationships, so default order is parent β†’ children in creation order.

Execution

Step 1: Analyze issues

User: "Let's work on QUI-2982 and its child issues using stacked PRs"

Claude: I'll fetch QUI-2982 and analyze its child issues.

Step 2: Determine work order

Dependency Graph (Work Order):

1. QUI-2982 - Implement user authentication
2. QUI-2983 - Add login form UI
   └─ Depends on: QUI-2982
3. QUI-2984 - Implement JWT token service
   └─ Depends on: QUI-2982
4. QUI-2985 - Add logout functionality
   └─ Depends on: QUI-2982

Step 3: Confirm with user

Claude: I've analyzed the issues. Here's the proposed work order:

1. QUI-2982 (Parent) - Implement user authentication
2. QUI-2983 (Child) - Add login form UI
3. QUI-2984 (Child) - Implement JWT token service
4. QUI-2985 (Child) - Add logout functionality

This will create a stack of 4 PRs. Shall I proceed?

Step 4: Execute (Issue 1)

# Checkout main and create branch for QUI-2982
git checkout main
git pull origin main
git checkout -b qui-2982-implement-user-authentication

# Work on the issue...
# (Make changes, write tests, etc.)

# Commit
git add .
git commit -m "feat: implement user authentication infrastructure

Add base authentication module and interfaces

Addresses QUI-2982

πŸ€– Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"

# Push and create PR
git push -u origin qui-2982-implement-user-authentication
gh pr create \
  --title "Implement user authentication" \
  --body "$(cat <<'EOF'
## Summary
Adds authentication infrastructure including user model and auth interfaces.

## Related Issue
Addresses QUI-2982 (https://linear.app/team/issue/QUI-2982)

## Stack Context
This PR is part of a stacked PR workflow:
- **Stack position:** 1/4
- **Base:** main
- **Next in stack:** QUI-2983 (login form UI)

## Stack Overview
1. QUI-2982 - Implement user authentication (this PR)
2. QUI-2983 - Add login form UI
3. QUI-2984 - Implement JWT token service
4. QUI-2985 - Add logout functionality

## Changes
- Added User model
- Created AuthService interface
- Added authentication middleware

## Test Plan
- Unit tests for User model
- Integration tests for authentication flow


EOF
)" \
  --base main

Add comment to Linear:

Use mcp__linear__create_comment:
{
  "issueId": "<qui-2982-uuid>",
  "body": "PR created: https://github.com/org/repo/pull/123\n\n"
}

Step 5: Execute (Issue 2)

# Create branch for QUI-2983 FROM current branch
git checkout -b qui-2983-add-login-form-ui

# Work on the issue...

# Commit
git add .
git commit -m "feat: add login form UI component

Create login form with validation

Addresses QUI-2983

πŸ€– Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"

# Push and create PR targeting previous branch
git push -u origin qui-2983-add-login-form-ui
gh pr create \
  --title "Add login form UI" \
  --body "..." \
  --base qui-2982-implement-user-authentication

Continue for remaining issues...

Result

Four PRs created:

  1. PR #123: QUI-2982 β†’ main
  2. PR #124: QUI-2983 β†’ qui-2982-implement-user-authentication
  3. PR #125: QUI-2984 β†’ qui-2983-add-login-form-ui
  4. PR #126: QUI-2985 β†’ qui-2984-implement-jwt-token-service

After First PR Merges

# PR #123 merged to main

# Update PR #124 to target main
gh pr edit 124 --base main

# Rebase QUI-2983 branch onto main
git checkout qui-2983-add-login-form-ui
git rebase main
git push --force-with-lease

Complex Dependency Graph

Scenario: Multiple issues with cross-dependencies and blocked-by relationships.

Setup

Linear Issues:

  • QUI-3000: Refactor database layer (Parent)
    • QUI-3001: Update User model schema
    • QUI-3002: Migrate existing data
      • Blocked by: QUI-3001
    • QUI-3003: Update API endpoints
      • Blocked by: QUI-3002
    • QUI-3004: Update frontend to use new API
      • Blocked by: QUI-3003
    • QUI-3005: Add new analytics queries
      • Blocked by: QUI-3001 (can work in parallel with migration)

Dependency Graph

QUI-3000 (Parent)
    β”œβ”€β†’ QUI-3001 (Update schema)
    β”‚       β”œβ”€β†’ QUI-3002 (Migrate data)
    β”‚       β”‚       └─→ QUI-3003 (Update API)
    β”‚       β”‚               └─→ QUI-3004 (Update frontend)
    β”‚       └─→ QUI-3005 (Analytics queries)

Work Order (Topological Sort)

1. QUI-3000 - Refactor database layer
2. QUI-3001 - Update User model schema
   └─ Depends on: QUI-3000
3. QUI-3002 - Migrate existing data
   └─ Depends on: QUI-3001
4. QUI-3003 - Update API endpoints
   └─ Depends on: QUI-3002
5. QUI-3004 - Update frontend to use new API
   └─ Depends on: QUI-3003
6. QUI-3005 - Add new analytics queries
   └─ Depends on: QUI-3001

Note: QUI-3005 could be worked on in parallel with QUI-3002/3003/3004 since it only depends on QUI-3001.

Execution Strategy

Option 1: Strict sequential (simplest) Work through all issues 1-6 in order, creating linear stack.

Option 2: Parallel branches (advanced) After completing QUI-3001:

  • Create branch A: QUI-3001 β†’ QUI-3002 β†’ QUI-3003 β†’ QUI-3004
  • Create branch B: QUI-3001 β†’ QUI-3005

This creates two parallel stacks from QUI-3001.

Example: Detecting the Complex Graph

# Use dependency_graph.py to visualize
python scripts/dependency_graph.py issues.json --visualize

Output:
Dependency Graph (Work Order):

1. QUI-3000 - Refactor database layer

2. QUI-3001 - Update User model schema
   └─ Depends on: QUI-3000

3. QUI-3002 - Migrate existing data
   └─ Depends on: QUI-3001

4. QUI-3005 - Add new analytics queries
   └─ Depends on: QUI-3001

5. QUI-3003 - Update API endpoints
   └─ Depends on: QUI-3002

6. QUI-3004 - Update frontend to use new API
   └─ Depends on: QUI-3003

Note: The topological sort may place QUI-3005 before QUI-3003 since they're in parallel branches.


Handling Blocked-By Relationships

Scenario: Issues explicitly block each other, requiring specific ordering.

Setup

Linear Issues:

  • QUI-4000: Performance optimization (Parent)
    • QUI-4001: Add performance monitoring
    • QUI-4002: Optimize database queries
      • Blocked by: QUI-4001 (need monitoring to measure improvements)
    • QUI-4003: Add caching layer
      • Blocked by: QUI-4002 (need optimized queries before caching)
    • QUI-4004: Update documentation
      • Blocked by: QUI-4003 (document final implementation)

Dependency Analysis

QUI-4000 β†’ QUI-4001 β†’ QUI-4002 β†’ QUI-4003 β†’ QUI-4004
(Parent)   (Monitor)  (Optimize) (Cache)    (Docs)

This is a perfect linear stack - each issue depends on exactly one previous issue.

Work Order

1. QUI-4000 - Performance optimization
2. QUI-4001 - Add performance monitoring
   └─ Depends on: QUI-4000
3. QUI-4002 - Optimize database queries
   └─ Depends on: QUI-4001
4. QUI-4003 - Add caching layer
   └─ Depends on: QUI-4002
5. QUI-4004 - Update documentation
   └─ Depends on: QUI-4003

PR Structure

PR #201: QUI-4000 β†’ main
PR #202: QUI-4001 β†’ qui-4000-performance-optimization
PR #203: QUI-4002 β†’ qui-4001-add-performance-monitoring
PR #204: QUI-4003 β†’ qui-4002-optimize-database-queries
PR #205: QUI-4004 β†’ qui-4003-add-caching-layer

Benefit of Explicit Dependencies

The blocked-by relationships ensure:

  • Reviewers understand the context and order
  • Each PR builds on verified improvements
  • No work proceeds until prerequisites are met

Working with Existing Branches

Scenario: Some issues already have branches or PRs started.

Setup

Linear Issues:

  • QUI-5000: Redesign dashboard (Parent)
    • QUI-5001: Update layout components (Already has branch qui-5001-layout)
    • QUI-5002: Add new widgets (No branch yet)
    • QUI-5003: Implement responsive design (No branch yet)

Execution

Step 1: Check for existing branches

# Check if branches exist
git fetch origin
git branch -r | grep -E "qui-500[0-3]"

Output:
origin/qui-5001-layout

Step 2: Handle existing branch

Claude: I found an existing branch for QUI-5001. Should I:
1. Use the existing branch and continue from there
2. Create a new branch (e.g., qui-5001-layout-v2)
3. Skip this issue

User: Use the existing branch

Step 3: Incorporate existing branch into stack

# Checkout and update existing branch
git checkout qui-5001-layout
git pull origin qui-5001-layout

# Verify it's up to date with main
git merge main

# Create PR if not exists
gh pr list | grep "qui-5001" || gh pr create \
  --title "Update layout components" \
  --body "..." \
  --base main

# Continue with next issue from this branch
git checkout -b qui-5002-add-new-widgets
# ... continue workflow

Result

  • PR for QUI-5001 updated to include stack metadata
  • New PRs for QUI-5002 and QUI-5003 created on top

Recovering from Conflicts

Scenario: A conflict occurs during rebase after bottom PR merges.

Setup

Stack:

  1. PR #301: QUI-6001 β†’ main (MERGED)
  2. PR #302: QUI-6002 β†’ main (rebase needed)
  3. PR #303: QUI-6003 β†’ qui-6002-branch (may need rebase after #302)

Problem

# After PR #301 merges
git checkout qui-6002-branch
git rebase main

# Conflict!
Auto-merging src/components/Button.tsx
CONFLICT (content): Merge conflict in src/components/Button.tsx

Resolution

Step 1: Resolve conflict

# View conflict
cat src/components/Button.tsx

# Resolve manually or with Claude's help
# Edit the file to resolve conflicts

# Mark as resolved
git add src/components/Button.tsx

# Continue rebase
git rebase --continue

Step 2: Verify changes

# Run tests
npm test

# Build
npm run build

Step 3: Update PR

# Force push (safe with --force-with-lease)
git push --force-with-lease

# Update PR base
gh pr edit 302 --base main

Step 4: Cascade to next PR

# Rebase QUI-6003 onto updated QUI-6002
git checkout qui-6003-branch
git rebase qui-6002-branch
git push --force-with-lease

Prevention Tips

  • Keep PRs small (< 250 lines)
  • Merge PRs quickly once approved
  • Communicate with team about overlapping changes
  • Use feature flags for large changes

Partial Stack Execution

Scenario: User wants to work on only some issues from a set.

Setup

Linear Issues:

  • QUI-7000: Improve error handling (Parent)
    • QUI-7001: Add error tracking
    • QUI-7002: Improve error messages
    • QUI-7003: Add error recovery
    • QUI-7004: Update documentation

User only wants to work on 7001 and 7002 now, save 7003 and 7004 for later.

Execution

Step 1: Analyze all issues

Claude: I've found 4 child issues for QUI-7000. Here's the dependency order:
1. QUI-7000 - Improve error handling
2. QUI-7001 - Add error tracking
3. QUI-7002 - Improve error messages
4. QUI-7003 - Add error recovery
5. QUI-7004 - Update documentation

Would you like to work on all of these, or a subset?

User: Just 7000, 7001, and 7002 for now

Step 2: Execute partial stack

# Work on QUI-7000
git checkout main
git checkout -b qui-7000-improve-error-handling
# ... work, commit, create PR

# Work on QUI-7001
git checkout -b qui-7001-add-error-tracking
# ... work, commit, create PR

# Work on QUI-7002
git checkout -b qui-7002-improve-error-messages
# ... work, commit, create PR

# Stop here (don't work on 7003 and 7004)

Step 3: Document for later

Claude: I've created PRs for QUI-7000, QUI-7001, and QUI-7002.

The remaining issues (QUI-7003 and QUI-7004) will depend on these PRs.
When you're ready to continue, you can either:
1. Run this skill again with QUI-7000 and specify issues 7003-7004
2. Manually create branches from qui-7002-improve-error-messages

Linear issues QUI-7003 and QUI-7004 have been noted with comments about the partial stack.

Result

  • Three PRs created and under review
  • Two issues remain for later work
  • Clear documentation of where to continue

Advanced Tips

Visualizing Complex Graphs

Use the dependency_graph.py script with Mermaid output for visual diagrams:

python scripts/dependency_graph.py issues.json --mermaid > graph.mmd

Then paste the output into a Mermaid viewer or GitHub issue.

Handling Priority Changes

If priorities change mid-stack:

  1. Pause current work
  2. Re-run dependency analysis with new priorities
  3. Reorder remaining work if needed
  4. Update PR stack metadata to reflect new order

Working with Multiple Stacks

If you have independent feature branches:

Stack A: Feature X          Stack B: Feature Y
β”œβ”€ PR #1 β†’ main            β”œβ”€ PR #5 β†’ main
β”œβ”€ PR #2 β†’ PR #1           β”œβ”€ PR #6 β†’ PR #5
└─ PR #3 β†’ PR #2           └─ PR #7 β†’ PR #6

Keep them separate and merge independently.

Auto-Merge Configuration

Enable GitHub's auto-merge for efficiency:

gh pr merge <pr-number> --auto --squash

This ensures PRs merge automatically when approved and checks pass.


Summary

This workflow enables:

  • Systematic tackling of related issues
  • Clear dependency management
  • Efficient code review process
  • Reduced blocking on large features

Key Principles:

  1. Always analyze dependencies before starting
  2. Keep each PR focused and small
  3. Review from bottom of stack upward
  4. Communicate clearly in PR descriptions
  5. Rebase promptly when upstream PRs merge

For more details, see SKILL.md and REFERENCE.md.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment