Tutorial: Your First Stacked PR
Learn how to split a large feature into reviewable chunks in 15 minutes
This hands-on tutorial teaches you how to use Stacked Pull Requests to break down a large feature into small, focused PRs that ship faster.
What You’ll Learn
Section titled “What You’ll Learn”- Create a stack of 3 dependent PRs
- Use the OCH CLI for efficient workflow
- Navigate the review process
- Merge the stack successfully
Time: 15-20 minutes Difficulty: Beginner
Prerequisites
Section titled “Prerequisites”Before starting, make sure you have:
- OpenCodeHub account
- A repository where you have write access
- Git installed locally
- OCH CLI installed (
npm install -g opencodehub-cli) - Basic Git knowledge (branch, commit, push)
First time with OCH CLI?
# Installnpm install -g opencodehub-cli
# Loginoch auth login# Enter your email and password when promptedThe Scenario
Section titled “The Scenario”You’re building a user authentication system. Instead of creating one massive PR, you’ll split it into 3 logical layers:
- Database Layer - User table schema
- Service Layer - Authentication logic
- API Layer - Login/logout endpoints
Each PR builds on the previous one, but can be reviewed independently.
Step 1: Set Up Your Workspace (2 min)
Section titled “Step 1: Set Up Your Workspace (2 min)”1.1. Clone your repository:
cd ~/projectsgit clone https://github.com/yourorg/your-repo.gitcd your-repo1.2. Ensure main is up to date:
git checkout maingit pull origin main1.3. Verify OCH CLI is connected:
och auth whoami# Should show: "Logged in as: your-email@example.com"Step 2: Create the Database Layer PR (4 min)
Section titled “Step 2: Create the Database Layer PR (4 min)”This is the foundation of your stack.
2.1. Create the first branch:
och stack create auth-databaseThis creates a new branch stack/auth-database from main.
2.2. Create the migration file:
mkdir -p db/migrationscat > db/migrations/001_create_users_table.sql << 'EOF'CREATE TABLE users ( id SERIAL PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
CREATE INDEX idx_users_email ON users(email);EOF2.3. Commit your changes:
git add db/migrations/git commit -m "feat: add users table migration
- Create users table with email and password_hash- Add email index for fast lookups- Set up timestamps"2.4. Submit to create the PR:
och stack submit
# Output:# ✓ Pushing stack/auth-database# ✓ Creating pull request## PR #123 created: feat: add users table migration# https://git.yourcompany.com/yourorg/your-repo/pulls/1232.5. Verify in web UI:
Open the PR link. You should see:
- Title: “feat: add users table migration”
- Files changed:
db/migrations/001_create_users_table.sql - Status: Draft or Ready for Review
Step 3: Create the Service Layer PR (4 min)
Section titled “Step 3: Create the Service Layer PR (4 min)”Now, build on top of your first PR.
3.1. Create the second stacked branch:
och stack create auth-serviceThis creates stack/auth-service from your current branch automatically.
3.2. Create the auth service:
mkdir -p src/servicescat > src/services/auth.ts << 'EOF'import bcrypt from 'bcrypt';import jwt from 'jsonwebtoken';import { db } from '../db';
export class AuthService { async register(email: string, password: string) { const hash = await bcrypt.hash(password, 10);
const user = await db.users.create({ email, password_hash: hash });
return { id: user.id, email: user.email }; }
async login(email: string, password: string) { const user = await db.users.findByEmail(email);
if (!user) { throw new Error('Invalid credentials'); }
const valid = await bcrypt.compare(password, user.password_hash);
if (!valid) { throw new Error('Invalid credentials'); }
const token = jwt.sign( { userId: user.id, email: user.email }, process.env.JWT_SECRET!, { expiresIn: '7d' } );
return { token, user: { id: user.id, email: user.email } }; }}EOF3.3. Commit:
git add src/services/git commit -m "feat: implement authentication service
- Add user registration with bcrypt hashing- Add login with JWT token generation- Implement password verification
Depends on: PR #123 (users table)"3.4. Submit:
och stack submit
# Output:# ✓ Pushing stack/auth-service# ✓ Creating pull request# ✓ Linking to base PR #123## PR #124 created: feat: implement authentication service# Stack: #123 → #124# https://git.yourcompany.com/yourorg/your-repo/pulls/1243.5. Check the stack:
och stack view
# Output:# 📚 Current Stack# ┌─ main (base)# ├─ #123: auth-database ⏳ Draft# └─ #124: auth-service ⏳ DraftPerfect! You now have a stack of 2 PRs.
Step 4: Create the API Layer PR (4 min)
Section titled “Step 4: Create the API Layer PR (4 min)”Final layer: the HTTP endpoints.
4.1. Create third branch:
och stack create auth-api4.2. Create API endpoints:
mkdir -p src/apicat > src/api/auth.ts << 'EOF'import { Router } from 'express';import { AuthService } from '../services/auth';
const router = Router();const authService = new AuthService();
router.post('/register', async (req, res) => { try { const { email, password } = req.body;
// Validation if (!email || !password) { return res.status(400).json({ error: 'Email and password required' }); }
const user = await authService.register(email, password);
res.status(201).json({ user }); } catch (error) { res.status(500).json({ error: error.message }); }});
router.post('/login', async (req, res) => { try { const { email, password } = req.body;
const result = await authService.login(email, password);
res.json(result); } catch (error) { res.status(401).json({ error: error.message }); }});
export default router;EOF4.3. Commit and submit:
git add src/api/git commit -m "feat: add authentication API endpoints
- POST /register - create new user- POST /login - authenticate and get token- Input validation- Error handling
Depends on: PR #124 (auth service)"
och stack submit
# Output:# ✓ Pushing stack/auth-api# ✓ Creating pull request# ✓ Linking to base PR #124## PR #125 created: feat: add authentication API endpoints# Stack: #123 → #124 → #1254.4. View complete stack:
och stack view
# Output:# 📚 Current Stack# ┌─ main (base)# ├─ #123: auth-database ⏳ Draft# ├─ #124: auth-service ⏳ Draft# └─ #125: auth-api ⏳ Draft (current)🎉 Congratulations! You’ve created your first stack of 3 PRs!
Step 5: Navigate the Review Process (3 min)
Section titled “Step 5: Navigate the Review Process (3 min)”Now let’s get these PRs reviewed and merged.
5.1. Mark PRs as ready:
# Mark first PR ready for reviewcurl -X PATCH https://git.yourcompany.com/api/prs/123 \ -H "Authorization: Bearer $TOKEN" \ -d '{"draft": false}'
# Or via web UI: Click "Ready for Review" on PR #1235.2. Request reviews:
In the web UI for PR #123:
- Click “Reviewers” → Select team members
- Add comment: “This is the first PR in a stack of 3. Reviewing these in order would be helpful!”
5.3. While waiting, add tests:
# Switch back to first branchgit checkout stack/auth-database
# Add test filecat > db/migrations/001_create_users_table.test.ts << 'EOF'import { test } from 'vitest';import { db } from '../db';
test('creates users table', async () => { // Run migration await db.migrate();
// Verify table exists const tables = await db.raw(` SELECT table_name FROM information_schema.tables WHERE table_name = 'users' `);
expect(tables.length).toBe(1);});EOF
git add .git commit -m "test: add migration test"git push
# PR #123 automatically updates!5.4. Monitor review progress:
# View PR detailsoch pr view 123
# Output:# #123 feat: add users table migration# State: OPEN# Author: @you# Branch: stack/auth-database → main# Created: 1/22/2026Step 6: Merge the Stack (2 min)
Section titled “Step 6: Merge the Stack (2 min)”Once PR #123 is approved, the magic happens!
6.1. Use the merge queue:
# Add individually as they get approved:och queue add 123 # Merges first# PR #124 becomes next in line after #123 mergesoch queue add 124 # Merges second# PR #125 becomes next in line after #124 mergesoch queue add 125 # Merges third6.2. Watch it merge automatically:
# Check queue statusoch queue list
# Output (refresh as needed):# Pos PR Title Status# 1 #123 Add users table migration running_ci# 2 #124 Auth service pending# 3 #125 Auth API pending6.3. Celebrate! 🎉
# Check final statusgit checkout maingit pull
git log --oneline -3
# Output:# abc123 feat: add authentication API endpoints# def456 feat: implement authentication service# ghi789 feat: add users table migrationAll 3 PRs merged! Your auth system is live!
What You’ve Learned
Section titled “What You’ve Learned”✅ How to create a stack of dependent PRs
✅ Using och stack create for easy stacking
✅ Submitting stacks with och stack submit
✅ Viewing stack visualization
✅ Using the merge queue for automatic merging
✅ How auto-rebasing works
Key Takeaways
Section titled “Key Takeaways”Stacked PRs are better because:
- Each PR was ~50-100 lines (easy to review)
- Reviews happened in parallel (faster)
- Each merge was low-risk (incremental)
- Clear history (logical progression)
vs. One Large PR:
- Would be ~200-300 lines
- Single review bottleneck
- High-risk merge
- Messy history
Next Steps
Section titled “Next Steps”Practice More
Section titled “Practice More”Try stacking your next feature:
- 🎨 Frontend feature → Split UI, logic, styles
- 🔧 Backend feature → Split models, services, controllers
- 📊 Data pipeline → Split ingestion, processing, output
Advanced Techniques
Section titled “Advanced Techniques”Learn more about:
Join the Community
Section titled “Join the Community”- 💬 Discord - Ask questions
- 📖 Documentation - Read more guides
- 🐛 GitHub - Report issues
Troubleshooting
Section titled “Troubleshooting”“och command not found”
# Install CLInpm install -g opencodehub-cli
# Or use npxnpx opencodehub-cli stack create auth-database“Permission denied”
# Login againoch auth login
# Verify permissionsoch auth whoami“Can’t create stack - conflicts”
# Make sure main is up to dategit checkout maingit pull
# Start freshgit checkout -b stack/auth-database main“PR not linking to stack”
# Manually link via web UI:# Go to PR → Settings → Dependencies → Select base PRCongratulations on completing your first stacked PR workflow! 🚀
You’re now ready to ship code faster with OpenCodeHub.