A TypeScript-based GitHub merge queue utility that automatically validates and merges approved PRs sequentially. This is a standalone reusable repository that provides custom GitHub Actions for any repository to use.
- Sequential Processing: Process one PR at a time to ensure each is tested against the latest master
- Auto-Update Branches: Automatically merge master into PR branches when they fall behind
- Smart Validation: Validates required checks, draft state, blocking labels, and merge conflicts
- Self-Service: Add to any repository without modifying the merge-queue codebase
- Configurable Trigger Label: Use the default "ready" label or choose your own via a repository variable
- Concurrency-Safe: Multiple PRs can be labeled simultaneously without issues
- Label a PR with "ready" (or your configured trigger label) to add it to the queue
- The queue manager validates the PR (checks passing, up-to-date)
- If the branch is behind master, it automatically merges master into the PR
- GitHub automatically re-runs tests after the update
- Once tests pass, the PR is automatically merged
- The queue moves to the next PR
Queue state is tracked entirely through GitHub labels — the queued-for-merge label means a PR is in the queue, and merge-processing means it's currently being worked on. No external state files or branches are needed.
- Queue Manager: Triggered by workflow_run after add/remove, plus self-dispatch while queue has items
- Queue State: Tracked via GitHub labels (
queued-for-merge,merge-processing) - Custom Actions: Three reusable GitHub Actions for queue management
add-to-queue: Validate a PR and add it to the queueprocess-queue: Process the next PR in the queueremove-from-queue: Remove a PR from the queue
/src/
/core/ # Core business logic
- github-api.ts # GitHub API wrapper
- pr-validator.ts # PR validation logic
- branch-updater.ts # Branch update logic
- merger.ts # PR merge logic
/actions/ # GitHub Action definitions
- add-to-queue/
- process-queue/
- remove-from-queue/
/utils/ # Logging, constants, errors
/types/ # TypeScript interfaces
To add the merge queue to your repository:
The merge queue authenticates via a GitHub App. Create one with these
permissions, install it on your target repositories, then store the App ID as a
repository variable (MERGE_QUEUE_APP_ID) and the private key as a secret
(MERGE_QUEUE_APP_PRIVATE_KEY).
| Permission | Access |
|---|---|
| Pull requests | Read & Write |
| Contents | Read & Write |
| Actions | Read & Write |
| Commit statuses | Read |
| Checks | Read |
| Metadata | Read |
See the Setup Guide for detailed step-by-step instructions.
Copy these three workflow files to your repository's .github/workflows/ directory:
merge-queue-entry.yml- Triggered when the queue trigger label is added (default: "ready")merge-queue-manager.yml- Triggered after entry/remove workflows + self-dispatchmerge-queue-remove.yml- Triggered when label is removed or PR is closed
See examples/target-repo-workflows/ for templates.
Add a PR to the queue by applying the trigger label (default: "ready"). The queue will automatically:
- Validate the PR
- Add the
queued-for-mergelabel - Process it when its turn comes
- Merge it when all checks pass
To use a custom trigger label instead of "ready", set a repository variable called MERGE_QUEUE_LABEL (Settings → Secrets and variables → Actions → Variables) to your preferred name. The example workflows reference this variable with a fallback, so everything stays in sync automatically.
Configure the queue behavior via workflow inputs:
with:
github-token: ${{ steps.app-token.outputs.token }}
queue-label: ${{ vars.MERGE_QUEUE_LABEL || 'ready' }} # Trigger label (configurable via repo variable)
merge-method: 'squash' # merge, squash, or rebase
auto-update-branch: true # Auto-merge master when behind
update-timeout-minutes: 30 # Max wait time for tests after updateTip: Set the
MERGE_QUEUE_LABELrepository variable to change the trigger label for your repository. If the variable is not set, all workflows default to "ready".
- Node.js 20.x or higher
- npm
npm installnpm run buildnpm testnpm run lintTarget repositories reference the actions by git tag (e.g. @v1), and GitHub
Actions runs the bundled dist/ files, not the TypeScript source. This means
every source change must be built, committed, and re-tagged before consumers pick
it up.
# 1. Make your source changes in /src
# e.g. edit src/core/github-api.ts
# 2. Build — compiles TypeScript and bundles each action with ncc
npm run build
# 3. Commit both source and dist changes
git add -A
git commit -m "Description of your change"
# 4. Move the floating major-version tag so consumers get the update
git tag -fa v1 -m "Release v1 — <short description>"
git push origin main --follow-tags
git push origin v1 --force
# For a new minor/patch release, also create a fixed tag:
git tag v1.1.0
git push origin v1.1.0Each action's action.yml declares runs.using: node20 with main: dist/index.js.
GitHub downloads the repository at the referenced tag and runs that file directly —
there is no build step at runtime. If the dist files are stale, consumers run old code.
| Tag | Purpose | Mutable? |
|---|---|---|
v1 |
Floating tag consumers reference (@v1) |
Yes |
v1.x.y |
Fixed release for auditability | No |
Target repositories reference actions like:
uses: BloomAndWild/merge-queue/src/actions/add-to-queue@v1The queue handles various failure scenarios:
- Checks failing: PR removed from queue with failure label
- Merge conflicts: PR removed with conflict label
- Tests fail after update: PR removed with detailed error message
- Manual merge: Queue detects and skips gracefully
- API errors: Retry with exponential backoff
Standard labels used by the queue:
ready- Trigger label to add PR to queue (configurable viaMERGE_QUEUE_LABELrepo variable)queued-for-merge- PR is waiting in queuemerge-processing- PR is currently being processedmerge-updating- PR branch is being updated with mastermerge-queue-failed- PR failed validation or testsmerge-queue-conflict- Merge conflict detected
- Never commit tokens, private keys, or secrets to git
- Authentication uses a GitHub App — installation tokens are short-lived (1 hour), auto-generated per workflow run, and scoped to specific repositories
- Install the App only on repositories that need the merge queue
- The merge queue always validates approvals at the application level (at least one approval, no outstanding changes requested)
- Validate all inputs from GitHub events
MIT
See CLAUDE.md for development guidelines.