SuperAPI AI Directory

claude-code-merge-queue

Agents & models

View repository

Updated 10-16 15:54

About this skill

Claude Code Merge Queue

Skill by 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

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

Or with other package managers:

pnpm add -D claude-code-merge-queue
yarn add -D claude-code-merge-queue
bun add -d claude-code-merge-queue

Quick Setup

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:

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:

# 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:

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:

npm run sync

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

claude-code-merge-queue promote

Ships integration branch to production:

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:

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:

// 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):

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:

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):

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:

export default {
  integrationBranch: "staging",    // Agents land here
  productionBranch: "main",        // promote ships to here
  checkCommand: "npm run check"
};
# 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

// 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"]
};
// 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

// 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

// 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:

# .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):

# 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:

# 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:

# 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

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

Build artifacts from other lanes

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

Agent Instructions

init adds this to CLAUDE.md:

## 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

# 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:

# .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):

# .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.