Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .agents/skills/mergepatrol/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
name: mergepatrol
description: This repo merges pull requests through Mergepatrol, a label-driven merge queue. Use when merging, landing or shipping a PR, queueing or dequeueing one, or reading a Mergepatrol comment or label on a PR.
---

# Mergepatrol

Pull requests in this repo land through Mergepatrol's merge queue. Queue a PR with a label and let Mergepatrol merge it; merging by hand or pushing to the default branch races the queue.

## The labels

The trigger label is `label` in `.mergepatrol/mergepatrol.yml` on the default branch, `mergepatrol` when the file leaves it out. Below, `<label>` stands for it.

- `<label>` queues the PR: `gh pr edit <number> --add-label <label>`. Removing it leaves the queue, and cancels the PR's run.
- `<label>:priority`, beside `<label>`, puts the PR at the front of the queue.
- `<label>:failed` is set by Mergepatrol when it ejects the PR.

## What the queue does

Mergepatrol rebases the PR onto the default branch, and may test it in a batch with other queued PRs. It resolves conflicts with an agent, runs the repo's `verify` commands, waits for the required checks, then merges with the repo's default merge method. When a required check fails, an agent commits a fix to the PR branch and the checks run again, a few times at most, unless `fix_ci` turns it off. The queue only serves PRs that target the default branch, and stacks of PRs (from `gh stack`) that merge into it.

A PR from a fork merges only as it was when the label went on: a push to it after that takes it out of the queue, until a maintainer adds `<label>` again. It never joins a batch, gets no agent, and is ejected on a conflict or a failed check. Mergepatrol can push its rebase only while the author allows edits by maintainers.

A stack is one item of the queue. The label on any of its PRs queues the whole stack, and the stack waits until every PR in it is ready. Mergepatrol rebases every layer when the stack is behind, and merges the stack all at once: every PR, or none. When it ejects a stack, every PR in it gets `<label>:failed`.

The rebase rewrites the PR branch, and a fix adds a commit to it. Before pushing more commits to a queued PR, fetch it and rebase your work onto the remote branch. A push to a queued PR makes Mergepatrol start the PR over.

## Reading its status

Mergepatrol keeps one comment on each queued PR up to date. It says one of:

- **On hold**: the PR is a draft, waits for a required review, or has changes requested. Mergepatrol comments the reason on the PR. It keeps its place, and runs once GitHub shows it ready.
- **Ejected**: the rebase, the checks or the merge failed, and the comment says which. The trigger label is gone and `<label>:failed` is on. Fix the cause, push, then add `<label>` again.
- **Merged**: done. The queue labels are removed.

## Settings

`.mergepatrol/mergepatrol.yml` sets the label, the batch size, the conflict resolver, the `verify` commands, and `fix_ci`. Mergepatrol reads it from the default branch, so a change takes effect once its PR merges.
38 changes: 38 additions & 0 deletions .claude/skills/mergepatrol/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
name: mergepatrol
description: This repo merges pull requests through Mergepatrol, a label-driven merge queue. Use when merging, landing or shipping a PR, queueing or dequeueing one, or reading a Mergepatrol comment or label on a PR.
---

# Mergepatrol

Pull requests in this repo land through Mergepatrol's merge queue. Queue a PR with a label and let Mergepatrol merge it; merging by hand or pushing to the default branch races the queue.

## The labels

The trigger label is `label` in `.mergepatrol/mergepatrol.yml` on the default branch, `mergepatrol` when the file leaves it out. Below, `<label>` stands for it.

- `<label>` queues the PR: `gh pr edit <number> --add-label <label>`. Removing it leaves the queue, and cancels the PR's run.
- `<label>:priority`, beside `<label>`, puts the PR at the front of the queue.
- `<label>:failed` is set by Mergepatrol when it ejects the PR.

## What the queue does

Mergepatrol rebases the PR onto the default branch, and may test it in a batch with other queued PRs. It resolves conflicts with an agent, runs the repo's `verify` commands, waits for the required checks, then merges with the repo's default merge method. When a required check fails, an agent commits a fix to the PR branch and the checks run again, a few times at most, unless `fix_ci` turns it off. The queue only serves PRs that target the default branch, and stacks of PRs (from `gh stack`) that merge into it.

A PR from a fork merges only as it was when the label went on: a push to it after that takes it out of the queue, until a maintainer adds `<label>` again. It never joins a batch, gets no agent, and is ejected on a conflict or a failed check. Mergepatrol can push its rebase only while the author allows edits by maintainers.

A stack is one item of the queue. The label on any of its PRs queues the whole stack, and the stack waits until every PR in it is ready. Mergepatrol rebases every layer when the stack is behind, and merges the stack all at once: every PR, or none. When it ejects a stack, every PR in it gets `<label>:failed`.

The rebase rewrites the PR branch, and a fix adds a commit to it. Before pushing more commits to a queued PR, fetch it and rebase your work onto the remote branch. A push to a queued PR makes Mergepatrol start the PR over.

## Reading its status

Mergepatrol keeps one comment on each queued PR up to date. It says one of:

- **On hold**: the PR is a draft, waits for a required review, or has changes requested. Mergepatrol comments the reason on the PR. It keeps its place, and runs once GitHub shows it ready.
- **Ejected**: the rebase, the checks or the merge failed, and the comment says which. The trigger label is gone and `<label>:failed` is on. Fix the cause, push, then add `<label>` again.
- **Merged**: done. The queue labels are removed.

## Settings

`.mergepatrol/mergepatrol.yml` sets the label, the batch size, the conflict resolver, the `verify` commands, and `fix_ci`. Mergepatrol reads it from the default branch, so a change takes effect once its PR merges.
23 changes: 22 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ permissions:
contents: read

jobs:
build:
checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -37,3 +37,24 @@ jobs:
# The recording tests encode real video.
- run: command -v ffmpeg || (sudo apt-get update && sudo apt-get install -y ffmpeg)
- run: pnpm test

determinism:
uses: ./.github/workflows/determinism.yml

# The branch ruleset requires `build` and nothing else, and Mergepatrol merges
# once required checks pass, so this job carries every check that must hold.
# It runs on `always()` and fails unless each need succeeded: a job skipped
# after a failed need counts as passed, to GitHub and to Mergepatrol alike.
build:
needs: [checks, determinism]
if: always()
runs-on: ubuntu-latest
steps:
- env:
RESULTS: ${{ toJSON(needs) }}
run: |
failed=$(echo "$RESULTS" | jq -r '[to_entries[] | select(.value.result != "success") | "\(.key): \(.value.result)"] | join(", ")')
if [ -n "$failed" ]; then
echo "::error::Required jobs did not succeed: $failed"
exit 1
fi
8 changes: 5 additions & 3 deletions .github/workflows/determinism.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
#
# So it runs on every commit, not nightly, and a failure here is treated as a
# product defect rather than a flake to be retried.
#
# CI calls it, and its required `build` check passes only when this does: a
# merge queue waits for required checks alone, so a separate workflow here
# would be advisory.
name: Determinism

on:
pull_request:
push:
branches: [main]
workflow_call:

permissions:
contents: read
Expand Down
21 changes: 21 additions & 0 deletions .mergepatrol/mergepatrol.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Mergepatrol reads this file from the default branch. The dashboard opens a PR to change it.
version: 1
label: mergepatrol
batch_size: 8
resolver:
provider: anthropic
model: claude-opus-5-5
effort: medium
# Run in Mergepatrol's job sandbox after an agent resolves a conflict or commits a fix, before
# anything is pushed, and offered to the agent to check its own work. They mirror CI's `build`
# job except `pnpm test`, which needs Chromium and ffmpeg that the sandbox lacks. `build`, a
# required check, still runs all of it on the head Mergepatrol pushes, before any merge.
verify:
- pnpm install --frozen-lockfile
- pnpm check --error-on-warnings
- pnpm knip
- pnpm typecheck
- pnpm --filter @bugpatrol/site typecheck
fix_ci:
enabled: true
attempts: 2
Loading