Development

Unblocking Your Dev Lane: Building a Non-Blocking Merge Queue in Azure

In the fast-paced world of software development, managing a high volume of pull requests (PRs) efficiently is crucial for maintaining velocity and positive software project metrics. A common bottleneck arises when a single failing PR blocks the entire merge lane, bringing development to a halt. This challenge was recently highlighted in a GitHub Community discussion, where developers explored building custom, non-blocking merge queues, particularly within Azure environments.

The Core Problem: Blocked Merge Lanes

The original poster sought a solution to automatically detect and eject failed PRs—due to CI failures, unmet required checks, policy violations, or merge conflicts—without impeding other valid PRs. The goal was to maintain an ordered, non-blocking flow, complete with retry logic for transient issues and priority handling. This isn't just about convenience; it directly impacts your team's throughput, morale, and ultimately, your delivery timelines.

Native Solution First: GitHub's Merge Queue

Before diving into custom implementations, a crucial piece of advice from the community was to first evaluate GitHub's native Merge Queue. Available on GitHub Team and Enterprise plans, this built-in feature is specifically designed to address these "non-blocking" requirements. It creates temporary branches for validation and automatically ejects failing PRs, potentially saving significant engineering effort. If your organization has access, it's often the most straightforward path to improving your software development metrics dashboard by reducing merge-related friction.

Architectural diagram of a custom Azure-backed merge queue for GitHub.
Architectural diagram of a custom Azure-backed merge queue for GitHub.

Building a Custom Non-Blocking Merge Queue: A "LinearB Free Alternative" Approach

When GitHub's native solution isn't an option, or if highly specific custom logic is required, the community provided robust blueprints for a custom merge queue. This approach essentially creates a "LinearB free alternative" for merge management, offering flexibility and control tailored to your unique development workflow.

High-Level Architecture: The Three Pillars

To build a truly non-blocking merge queue, you'll need three core components working in concert:

  1. A Queue of PRs: This stores pull requests with rich metadata, ordered by priority or labels, and tracks their current status and retry attempts.

  2. A Dedicated Processor: This worker handles one PR at a time, per target branch, ensuring sequential and safe merging.

  3. Intelligent Decision Logic: This determines whether a PR passes validation, needs a retry, or should be ejected from the queue.

Visualizing a non-blocking merge queue: failed PRs are ejected, allowing valid PRs to proceed.
Visualizing a non-blocking merge queue: failed PRs are ejected, allowing valid PRs to proceed.

Diving Deeper: Implementation Details

1. Event Triggers: Listening to GitHub

Your system needs to react to changes. GitHub webhooks are your best friend here. You'll want to listen for events such as:

  • pull_request (opened, synchronized, closed)
  • check_suite / status (CI/CD checks, required status checks)
  • pull_request_review (for required approvals)

These events will trigger your queueing mechanism, adding new PRs or updating the status of existing ones.

2. The Queue and Backing Store

For your queue, you'll need a reliable backing store to persist PR metadata. Options like Azure Table Storage, Azure Service Bus, Redis, or even GitHub Issues (for simpler setups) can serve this purpose. Store critical information:

  • PR number and target branch
  • Priority or labels
  • Status of required checks
  • Mergeability status
  • Retry count and last validation timestamp

3. The Worker Process: Azure Functions & Durable Functions

This is the brain of your merge queue. An Azure Function (or GitHub Action) will act as your worker. A crucial design principle here is to have a single worker per target branch to prevent race conditions and ensure strict sequential processing.

For robust retry logic and state management, Azure Durable Functions come highly recommended. Their orchestrator pattern excels at managing long-running workflows, allowing you to implement smart retries with exponential backoff (e.g., waiting 5 minutes, then 15, then 30) without keeping a server actively billed during wait times. This is far more efficient than stateless functions for this use case.

4. Validation and Merge Logic: Always Revalidate

The community emphasized a critical point: always revalidate everything just before attempting a merge. This prevents merging stale PRs or those that have become invalid due to subsequent changes on the target branch. Your worker should:

  • Pull the next PR from the queue.
  • Use the GitHub REST API to verify:
    • All required status checks are passing (GET /repos/{owner}/{repo}/commits/{sha}/status).
    • There are no merge conflicts (check mergeability via API).
    • All required policy validations are met.
    • Required approvals are present.
  • If all checks pass, merge the PR (PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge).

5. The "Don't Block the Lane" Principle & Decision Logic

This is where the "non-blocking" magic happens. Your processor's decision logic is key:

  • If everything passes: Merge the PR and move on to the next one in the queue.

  • If it's a permanent failure (e.g., policy violation, merge conflict): Eject the PR from the queue immediately. It requires manual intervention and should not block the lane.

  • If it's a transient failure (e.g., flaky CI test): Re-queue the PR with exponential backoff. After a predefined number of attempts (e.g., 3-5), if it still fails, eject it as a permanent failure.

The core idea is that an invalid PR is never allowed to stall the entire queue. It's either merged, retried (for transient issues), or removed, ensuring continuous flow.

Implementation Options

While Azure Functions (especially Durable Functions) offer a robust and scalable solution, you could also orchestrate this using GitHub Actions combined with a simple queueing mechanism (like Redis or even GitHub Issues for state tracking). The choice often depends on your team's existing expertise and infrastructure.

The Payoff: Predictable Delivery and Enhanced Productivity

Implementing a non-blocking merge queue, whether native or custom, transforms your development workflow. It leads to:

  • Increased Throughput: Valid PRs merge faster, accelerating delivery.
  • Reduced Frustration: Developers spend less time waiting for blocked lanes.
  • Predictable Release Cycles: Your software project metrics become more consistent and reliable.
  • Better Resource Utilization: CI/CD pipelines run more efficiently.

Conclusion

The GitHub Community discussion highlighted a universal challenge and offered practical, actionable solutions. Before embarking on a custom build, always assess GitHub's native Merge Queue. If a custom solution is necessary, leveraging Azure Functions (especially Durable Functions) with a well-defined queue, a single-branch processor, and strict revalidation logic provides a powerful "LinearB free alternative" to keep your dev lane flowing smoothly. Empower your teams with tooling that removes bottlenecks, fostering a more productive and predictable development environment.

Share:

Track, Analyze and Optimize Your Software DeveEx!

Effortlessly implement gamification, pre-generated performance reviews and retrospective, work quality analytics, alerts on top of your code repository activity

 Install GitHub App to Start
devActivity Screenshot