Skip to content

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.

  • 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


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?

Terminal window
# Install
npm install -g opencodehub-cli
# Login
och auth login
# Enter your email and password when prompted

You’re building a user authentication system. Instead of creating one massive PR, you’ll split it into 3 logical layers:

  1. Database Layer - User table schema
  2. Service Layer - Authentication logic
  3. API Layer - Login/logout endpoints

Each PR builds on the previous one, but can be reviewed independently.


1.1. Clone your repository:

Terminal window
cd ~/projects
git clone https://github.com/yourorg/your-repo.git
cd your-repo

1.2. Ensure main is up to date:

Terminal window
git checkout main
git pull origin main

1.3. Verify OCH CLI is connected:

Terminal window
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:

Terminal window
och stack create auth-database

This creates a new branch stack/auth-database from main.

2.2. Create the migration file:

Terminal window
mkdir -p db/migrations
cat > 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);
EOF

2.3. Commit your changes:

Terminal window
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:

Terminal window
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/123

2.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:

Terminal window
och stack create auth-service

This creates stack/auth-service from your current branch automatically.

3.2. Create the auth service:

Terminal window
mkdir -p src/services
cat > 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 } };
}
}
EOF

3.3. Commit:

Terminal window
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:

Terminal window
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/124

3.5. Check the stack:

Terminal window
och stack view
# Output:
# 📚 Current Stack
# ┌─ main (base)
# ├─ #123: auth-database ⏳ Draft
# └─ #124: auth-service ⏳ Draft

Perfect! You now have a stack of 2 PRs.


Final layer: the HTTP endpoints.

4.1. Create third branch:

Terminal window
och stack create auth-api

4.2. Create API endpoints:

Terminal window
mkdir -p src/api
cat > 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;
EOF

4.3. Commit and submit:

Terminal window
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 → #125

4.4. View complete stack:

Terminal window
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:

Terminal window
# Mark first PR ready for review
curl -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 #123

5.2. Request reviews:

In the web UI for PR #123:

  1. Click “Reviewers” → Select team members
  2. 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:

Terminal window
# Switch back to first branch
git checkout stack/auth-database
# Add test file
cat > 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:

Terminal window
# View PR details
och pr view 123
# Output:
# #123 feat: add users table migration
# State: OPEN
# Author: @you
# Branch: stack/auth-database → main
# Created: 1/22/2026

Once PR #123 is approved, the magic happens!

6.1. Use the merge queue:

Terminal window
# Add individually as they get approved:
och queue add 123 # Merges first
# PR #124 becomes next in line after #123 merges
och queue add 124 # Merges second
# PR #125 becomes next in line after #124 merges
och queue add 125 # Merges third

6.2. Watch it merge automatically:

Terminal window
# Check queue status
och 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 pending

6.3. Celebrate! 🎉

Terminal window
# Check final status
git checkout main
git pull
git log --oneline -3
# Output:
# abc123 feat: add authentication API endpoints
# def456 feat: implement authentication service
# ghi789 feat: add users table migration

All 3 PRs merged! Your auth system is live!


✅ 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

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

Try stacking your next feature:

  • 🎨 Frontend feature → Split UI, logic, styles
  • 🔧 Backend feature → Split models, services, controllers
  • 📊 Data pipeline → Split ingestion, processing, output

Learn more about:


“och command not found”

Terminal window
# Install CLI
npm install -g opencodehub-cli
# Or use npx
npx opencodehub-cli stack create auth-database

“Permission denied”

Terminal window
# Login again
och auth login
# Verify permissions
och auth whoami

“Can’t create stack - conflicts”

Terminal window
# Make sure main is up to date
git checkout main
git pull
# Start fresh
git checkout -b stack/auth-database main

“PR not linking to stack”

Terminal window
# Manually link via web UI:
# Go to PR → Settings → Dependencies → Select base PR

Congratulations on completing your first stacked PR workflow! 🚀

You’re now ready to ship code faster with OpenCodeHub.