# Claude Code Merge Queue

> Local FIFO merge queue for parallel Claude Code agents with zero runtime dependencies

- Skill: `aradotso/claude-code-merge-queue` (Agent Skill)
- Install (CLI): `npx skillmds@latest add aradotso/claude-code-merge-queue`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aradotso/claude-code-merge-queue/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: aradotso (https://skillmd.com/u/aradotso)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/aradotso/claude-code-merge-queue

---


# Claude Code Merge Queue

> Skill by [ara.so](https://ara.so) — Claude Code Skills collection.

A local merge queue that coordinates parallel Claude Code agents. Prevents race conditions when multiple isolated worktrees try to land changes simultaneously.

## What It Solves

When running multiple `claude --worktree` sessions:
- **Push races**: Multiple agents push to the same branch, causing rejected pushes and rebases
- **Build contention**: Concurrent builds overload the machine
- **Test conflicts**: Tests hitting shared resources (databases, ports) interfere with each other

Claude Code Merge Queue serializes landings through a FIFO queue and coordinates builds across all lanes.

## Installation

```bash
npm install --save-dev claude-code-merge-queue
```

Or with other package managers:
```bash
pnpm add -D claude-code-merge-queue
yarn add -D claude-code-merge-queue
bun add -d claude-code-merge-queue
```

## Quick Setup

```bash
npx claude-code-merge-queue init
```

This auto-configures:
- **`claude-code-merge-queue.config.mjs`** — detects integration branch and check command
- **`CLAUDE.md`** — instructions for agents to land automatically
- **`.claude/settings.json`** — WorktreeCreate hook registration
- **`.husky/pre-push`** — enforces queue usage
- **`package.json` scripts** — `land`, `sync`, `promote`, `preview` commands
- **Preflight safety** — catches stale branches before push

After init, **commit all changes** and start using:
```bash
claude --worktree feature-name
```

## Core Commands

### `claude-code-merge-queue hook worktree-create`

Claude Code WorktreeCreate hook. Automatically creates numbered lanes when you run `claude --worktree`:

```bash
# In .claude/settings.json (init does this):
{
  "hooks": {
    "WorktreeCreate": "claude-code-merge-queue hook worktree-create"
  }
}
```

Creates worktrees as `../project-lane-1`, `../project-lane-2`, etc.

### `claude-code-merge-queue land`

Rebases current lane onto integration branch and pushes through FIFO queue:

```bash
npm run land
# or directly:
claude-code-merge-queue land
```

**Serialization**: Only one lane pushes at a time, machine-wide.

**Checks run first**: `checkCommand` must pass before push proceeds.

**Auto-cleanup**: Removes already-landed sibling lane worktrees.

Agents run this themselves after their changes pass local tests.

### `claude-code-merge-queue sync`

Fast-forwards main checkout to latest and reinstalls dependencies if needed:

```bash
npm run sync
```

Run after landing to see changes in your main dev server.

### `claude-code-merge-queue promote`

Ships integration branch to production:

```bash
npm run promote
```

**Human-only command** — never in agent instructions.

### `claude-code-merge-queue preview`

Mirrors a lane's working tree (including uncommitted changes) onto main checkout:

```bash
npm run preview
# Then switch back:
npm run preview:restore
```

For quick inspection without rebuilding.

### `claude-code-merge-queue build-lock -- <cmd>`

Serializes heavy build commands across all lanes:

```typescript
// In package.json:
{
  "scripts": {
    "build": "claude-code-merge-queue build-lock -- vite build",
    "check": "claude-code-merge-queue build-lock -- npm run type-check && npm test"
  }
}
```

Prevents concurrent builds from overwhelming the machine.

### `claude-code-merge-queue port`

Returns lane's dev server port (derived from lane number):

```bash
PORT=$(claude-code-merge-queue port)
npm run dev -- --port $PORT
```

With `portBase: 3000`, lane-1 gets 3001, lane-2 gets 3002, etc.

### `claude-code-merge-queue prune`

Removes already-landed sibling worktrees immediately:

```bash
claude-code-merge-queue prune
```

`land` does this automatically, but this is useful for manual cleanup.

## Configuration

**`claude-code-merge-queue.config.mjs`** (ES module format):

```javascript
export default {
  // Lane naming
  branchPrefix: "lane/",           // Creates lane/1, lane/2
  worktreeSuffix: "-lane-",        // Creates ../project-lane-1
  
  // Port allocation
  portBase: 3000,                  // lane-1 → 3001, lane-2 → 3002
  
  // Branch strategy
  integrationBranch: "main",       // Where agents land
  productionBranch: null,          // Set for staging→prod model
  protectedBranches: [],           // Extra protected branches
  
  // Landing requirements
  checkCommand: "npm run check",   // Must pass before landing
  checksRequired: true,            // false = no checks (explicit opt-out)
  
  // Build coordination
  regenerableFiles: [              // Never block rebase on these
    "package-lock.json",
    "pnpm-lock.yaml"
  ],
  buildOutputDirs: [               // preview skips these
    "dist",
    "build",
    ".next"
  ],
  
  // Shared resources
  symlinks: [                      // Symlinked from main checkout
    ".env",
    ".env.local",
    "node_modules"
  ]
};
```

### Two-Stage Branch Model

For staging→production workflow:

```javascript
export default {
  integrationBranch: "staging",    // Agents land here
  productionBranch: "main",        // promote ships to here
  checkCommand: "npm run check"
};
```

```bash
# Agents land to staging:
npm run land

# You decide when to ship:
npm run promote  # staging → main
```

## Real-World Patterns

### TypeScript Project with Full Checks

```javascript
// claude-code-merge-queue.config.mjs
export default {
  branchPrefix: "lane/",
  worktreeSuffix: "-lane-",
  portBase: 3000,
  integrationBranch: "main",
  productionBranch: null,
  checkCommand: "npm run check:push",
  checksRequired: true,
  symlinks: [".env", "node_modules"],
  buildOutputDirs: ["dist"],
  regenerableFiles: ["package-lock.json"]
};
```

```json
// package.json
{
  "scripts": {
    "type-check": "tsc --noEmit",
    "lint": "eslint src",
    "test": "vitest run",
    "build": "claude-code-merge-queue build-lock -- tsc && vite build",
    "check:push": "npm run type-check && npm run lint && npm run test",
    "land": "claude-code-merge-queue land",
    "sync": "claude-code-merge-queue sync",
    "promote": "claude-code-merge-queue promote"
  }
}
```

### React App with Separate Staging

```javascript
// claude-code-merge-queue.config.mjs
export default {
  branchPrefix: "lane/",
  worktreeSuffix: "-lane-",
  portBase: 3000,
  integrationBranch: "staging",
  productionBranch: "main",
  checkCommand: "npm run check",
  checksRequired: true,
  symlinks: [".env.local", "node_modules"],
  buildOutputDirs: [".next", "out"],
  regenerableFiles: ["package-lock.json"]
};
```

### Monorepo with Shared Database Tests

```typescript
// examples/ephemeral-tmp-dir.example.ts
import { registerEphemeralResource } from 'claude-code-merge-queue/ephemeral';
import { spawn } from 'child_process';
import { mkdtemp, rm } from 'fs/promises';
import { tmpdir } from 'os';
import { join } from 'path';

// Each lane gets isolated database directory
const dbDir = await registerEphemeralResource({
  async create() {
    const dir = await mkdtemp(join(tmpdir(), 'test-db-'));
    
    // Start postgres in this directory
    const pg = spawn('postgres', ['-D', dir], {
      detached: true,
      stdio: 'ignore'
    });
    pg.unref();
    
    return { path: dir, pid: pg.pid };
  },
  async destroy(resource) {
    // Kill postgres
    try {
      process.kill(resource.pid);
    } catch {}
    
    // Remove directory
    await rm(resource.path, { recursive: true, force: true });
  }
});

// Use in tests
process.env.TEST_DB_PATH = dbDir.path;
```

## Pre-Push Hook

The hook enforces queue usage and runs checks:

```bash
# .husky/pre-push (init creates this)
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

claude-code-merge-queue hook pre-push "$@"
```

Blocks:
- Direct pushes to integration branch → "Use `npm run land` instead"
- Pushes to production branch → "Use `npm run promote` instead"
- Any push without passing `checkCommand`

## Emergency Override

For genuine emergencies (init creates this):

```bash
# Bypass queue (branch name required for safety):
CLAUDE_CODE_MERGE_QUEUE_EMERGENCY_PUSH=1 git push origin HEAD:main
```

This is a convention, not cryptographic enforcement.

## Troubleshooting

### "command not found: claude-code-merge-queue"

Lane is stale relative to origin. The preflight script catches this:

```bash
# Rebase onto latest integration branch:
git fetch origin
git rebase origin/main
```

### Stale lock preventing land

If a process crashes mid-land, the next land detects the PID is dead and reclaims:

```bash
# Just retry:
npm run land
```

### Tests fail in lane but pass locally

Likely shared resource conflict. Check for:
- Same port numbers across lanes
- Shared database without isolation
- Temp files in predictable locations

Use `claude-code-merge-queue port` for ports and `ephemeral.ts` for databases.

### Lane out of sync after rebase

```bash
# In lane worktree:
npm run sync  # Updates main checkout
git rebase origin/main
```

### Build artifacts from other lanes

```bash
# Clean and rebuild:
rm -rf dist build .next
npm run build
```

## Agent Instructions

`init` adds this to `CLAUDE.md`:

```markdown
## Landing Changes

When your changes are complete and tests pass:

1. Run `npm run land` to push through the merge queue
2. If it succeeds, your work is done
3. If checks fail, fix them and retry

Never use `git push` directly to main/staging.
```

This makes landings automatic without human intervention.

## Architecture Notes

### Queue Implementation

FIFO queue uses filesystem locks in `os.tmpdir()`:
- Atomic operations via exclusive file creation
- PID tagging for crash safety
- No stale locks — liveness check reclaims dead PIDs

### Worktree Structure

```
project/              # Main checkout
project-lane-1/       # Worktree for lane/1
project-lane-2/       # Worktree for lane/2
```

Symlinks for shared resources:
- `node_modules` → avoids duplicate installs
- `.env*` → shares secrets
- Custom paths via config

### Zero Runtime Dependencies

No `node_modules` needed in production. All locking and coordination uses:
- Built-in `fs` module
- Process ID checks
- Git's native worktree support

## Common Commands Quick Reference

```bash
# Initial setup
npx claude-code-merge-queue init

# Daily workflow
claude --worktree feature-x        # Start isolated lane
npm run land                       # Land after tests pass
npm run sync                       # Update main checkout

# Preview without build
npm run preview                    # Mirror lane to main
npm run preview:restore            # Switch back

# Release
npm run promote                    # Ship to production

# Utilities
claude-code-merge-queue port       # Get lane's port number
claude-code-merge-queue prune      # Clean landed lanes
```

## Integration with CI

On integration branch:

```yaml
# .github/workflows/ci.yml
name: CI
on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npm run check
```

On production branch (if using two-stage):

```yaml
# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build
      - run: ./deploy.sh
```

The queue handles local coordination; CI is your second checkpoint for landed code.

