Merge queue landing notices
yoke merge item PREFIX-N verifies the exact candidate and arms its pull
request. A headless worker receives landing_pending=true and waits for the
control plane's landing notice. Re-enter the same merge command when that
notice arrives, retaining its result and verification evidence.
Merge preflight refuses when the lane's .yoke/merge-queue.json differs from
live enforcement rules. It reads the gate branch's declaration before teaching
recovery: when origin/main already matches live, update this lane from
origin/main, re-verify, and retry the merge. Never apply a stale lane's
declaration: that would change the rules for every pull request. When the gate
branch also differs from live, run yoke github merge-queue apply --project P
from an up-to-date checkout of that branch, then retry. Projects targeting a
branch other than main use that gate branch. gate_declaration_unverified
means the gate declaration or live rules could not be compared: restore read
access and a valid declaration on the gate branch, then retry without applying
from the lane.
Before it selects a route, the command asks the item's integration dependency
gate (merge.preflight.dependency_gate). Queue admission only sees
predecessors that are queue members, so this gate is what keeps an item from
enqueuing while a predecessor is still being implemented. A blocked gate
refuses with integration_dependency_blocked, naming each predecessor and its
status; nothing is armed or enqueued. Wait for the predecessor's landing
notice, then re-run the same command. A gate that cannot be read refuses with
integration_dependency_gate_unavailable rather than landing.
Control-plane upkeep reads the registered pull request about once a minute.
It runs with the Yoke API server's health refresh or, for a machine-local
universe, on each cycle of its standing relay.
Relay claims never read GitHub. A waiting worker's
merge_queue.landing.observe call returns the stored landing record without reading GitHub while that record is newer than
two refresh intervals. When upkeep has not kept it current, for example on
a machine-local universe with no relay running, the call refreshes the project
from GitHub itself, so a merge is reported within about two refresh intervals.
Close-out reads GitHub again whenever the stored record is not LANDED,
so a record written just before the merge never holds close-out. A recorded admission or a pending observation that GitHub armed or
queued that same pull request requires a full queue read even after GitHub
clears merge-when-ready. This distinguishes a stopped landing from a pull
request that was opened for verification and has never been armed. The durable
landing record supplies this fact; diagnostic events do not.
Before replacing that held observation, the observer retains its episode in
the existing item landing marker until the stopped notice reaches its holder.
This keeps a failed notice transport retryable on the next observation.
When GitHub holds neither arming nor a viable queue entry, or the entry state
is UNMERGEABLE with a decided mergeable, the observer sends one stopped-landing notice to the item's claim holder through the existing wake
route. An explicit yoke github merge-queue hold <item> stays waiting without
a stopped notice, including after a new head is pushed, until the PR is armed
or queued again. If automatic disarming failed, the notice names that same
hold command: run it before pushing a fix or re-entering yoke merge item.
Required checks that failed or were cancelled appear in the notice;
the stop is still reported if a later rerun passed without restoring arming.
mergeable=CONFLICTING means the PR conflicts with the base: rebase onto the
base, re-run verification and re-enter yoke merge item. An UNMERGEABLE
queue entry with mergeable=MERGEABLE can conflict only with the train ahead.
The landing record snapshots the preceding PR numbers and shared changed
files. Its notice asks the holder for no action: the observer re-enqueues the
unchanged head itself, and a hold would switch that off. Shared files explain
possible overlap; they do not prove a content conflict.
An unreadable queue membership proves no stop and is retried on the next
observation. So does an UNMERGEABLE entry while GitHub still reports
mergeable=UNKNOWN, and an open pull request whose merge state GitHub reports as
unknown: GitHub reports that while it recomputes, including mid-merge after it
clears the queue entry and merge-when-ready, so the observation stays pending
until a later read shows the merge or a known refusal. A merged pull request receives the normal completion notice.
Recovery belongs to the holder: address the named check or protection rule
and re-run the same governed yoke merge item command. If the lane or base
moved, rebase onto the base, re-run verification, and re-enter that command.
For a snapshotted queue conflict, the observer waits for the entries ahead to
leave, rechecks base mergeability and required checks, and requests
merge-when-ready again on the unchanged head if it remains mergeable.
yoke merge item --wait consumes that recovery through the same server
record; it returns control to the lane holder when a rebase is needed.
An unreadable queue or file listing names the failed read and its repair;
no re-enqueue is sent without the needed evidence. Other stopped landings
require the holder's governed retry. A governed retry recovers cancelled
or never-started required checks even when the open PR is already armed but
unadmitted; it never sends a duplicate arming request. It verifies current
head, newest workflow run and job evidence before one rerun POST. Active,
successful, superseded and enqueued attempts are protected. After three
cancelled attempts, inspect job annotations and fix the runner or workflow
limit before starting a fresh declared attempt. An accepted rerun is not a
pass: required checks must be replaced on the exact candidate. A new
workflow_dispatch is a fresh attempt whose head and replacement need proof. Repeated observation reuses the
same stop notice; a new governed arming starts a new episode.
Failed merge trains
After inspecting the failed run, a worker may claim one retry when the change cannot
explain the test failure. First message yoke say --steering with the test and CI run URL,
then re-run the same complete yoke merge item command with
--retry-flake 'TEST: why the change cannot explain the failure'.
The item records its pull request, head/base, run URL, reason and session before arming.
A second claim on that input pair refuses with merge_train_flake_retry_exhausted;
a second failure belongs to the item and requires a fix. The observer never claims a
flake automatically. A rejected queue-entry request releases that attempt's reservation;
an accepted or uncertain request keeps it spent. A failed release names its reservation
and repair; inspect Merge train retries before recovery. If the provider omits the
run's web URL, the reader derives it from the identified Actions run id and repository.
See the
verification policy.
An older serving build refuses this reservation with its serving floor; ask the
control-plane operator for a serving release that carries the function. Ordinary merges
remain available without this optional retry.
Automatic conflict re-entry requests Contents: write and Pull requests: write on its
installation token. If GitHub refuses access, restore those App permissions and token
scopes, then retry the governed merge. Each accepted automatic request is saved in the
item's Queue re-entry receipts section, including the pull request, head and acceptance
time; read it with yoke items section get PREFIX-N --section 'Queue re-entry receipts'.
Post-release proof requires a real receipt accepted after that release; unit tests alone
cannot establish it.
Re-landing an item that already landed
An item keeps its current landing until a new pull request actually merges.
When the merge boundary records a pull request for an item that already
landed (its merged_at or merge_queue_landed_at is set), the pull request
becomes a re-land attempt in
merge_queue_landing_attempts
and the item's merge_queue_* columns stay on the previous landing. One
attempt is open per item; a newer pull request closes the older attempt as
superseded. Opening a never-armed attempt preserves the prior landed
observation; closing that attempt unmerged leaves it available. Arming a new
attempt starts a new observation episode. Leaving the queue clears the
attempt's enqueued_at; the durable recovery record keeps the queue blocker
and shared files. Release enrollment and containment refuse a current run
member's open attempt with item_landing_open, naming its PR. Wait for the
correction to merge or close, then refresh the candidate and retry release
admission; the older landing cannot stand in for a correction still in flight.
A member removed with yoke deployment-runs remove-item no longer blocks the
run: its open correction rides a later release.
When a lane adds no content to its target, re-run yoke merge item PREFIX-N
with --result and --verification. Close-out verifies the newest completed
Merge Receipt against the fetched target and uses the pull request that
carried that merge. A newer merge carrying the lane candidate still uses its
own landing observation instead of an older receipt. A later open, unarmed pull request needs no landing
observation and is not armed again. merge_recorded_landing_missing means
no completed identity can be read: preserve the lane and ask the control-plane
operator to restore its recorded landing, then retry. An ancestry or carrier
read refusal names the checkout or provider access to repair.
The observer reads the open attempt instead of the delivered landing. When
the attempt merges, the observer moves the item's columns onto it, stamps
GitHub's merge time and sends the normal completion notice. When it closes
unmerged, the attempt closes with the reason pull request N closed without merging; the item keeps its previous landing, the item's landing is
unchanged. An armed attempt first sends its holder the stopped-landing notice
and closes once that notice is delivered, so a failed notice is retried.
The board label, the fleet landing readback, github.merge_queue.readiness,
github.merge_queue.hold and the waiting worker describe the open attempt, so
holding a re-land holds and confirms its own pull request. An armed open
attempt also counts as the holder's landing wait: the turn-end Stop gate lets
the holder stop without a continuation or steering alarm, and the session
roster and fleet idle-holder check treat it as awaiting landing. An attempt
that is open but not armed, or closed, leaves the earlier landing in charge,
so the holder still owes close-out. Close-out records
the landing under the pull request it went through, not the delivered one.
yoke items detail get shows the current landing under merge_queue and the
open or last closed attempt, with its reason, under merge_queue.attempt.
The harness manifest determines whether the existing wake route can resume the holder automatically. Operator-driven surfaces need the operator to re-enter the session. If the holder is gone, the notice routes to the project's steering seat for restaffing.
The observer saves the current PR's head branch, target, head SHA and merge
SHA in the item's existing Merge Receipts document in the same transaction as
its landing stamps, before sending the completion notice. Missing identity
leaves the landing unstamped and records merge_landing_identity_missing in
the project refresh error; retry with a complete GitHub PR readback. Release
containment can use this identity while worker closeout is still pending.
The worker still records results and verification and enriches that exact
receipt with changed files and contributed commits. Re-observation is
idempotent; a later landing preserves the earlier receipt's identity.
During worker closeout, the merge boundary fetches the remote default branch and requires it to contain the exact candidate commit. The recorded merge SHA is the merge on that fetched branch's first-parent history that first carried the candidate: the item's own pull request merge. A merge-queue batch's merge commit is the train's tip, so in a multi-PR batch it names the last member's merge and never stands in for another member's landing. A later target push cannot become the item's landing either. Local merges record the merge that first carried the candidate, or the candidate itself for a fast forward. A failed fetch or missing candidate returns a named refusal, retains the lane, and leaves the item before landing. Repair routing or remote access and re-enter the same governed merge command. A project with no remote proves candidate ancestry on its local default branch.
Epic lanes
In a project that declares the merge queue, merge-worktree lands each epic
lane through the same queue route as yoke merge item, recorded on the epic
item. The queue retests the lane against the base branch when its train
forms, so items landing during the lane's checks cannot make it stale. After
the first lane lands, each later lane's pull request is a re-land attempt
(see above). A headless worker gets landing_pending with exit 9: the lane is
armed, not landed. When the landing notice arrives, re-run the same
yoke watch merge merge-worktree command to record the queue close-out, the
lane landing and lane cleanup. A failing capability probe refuses with
merge_queue_probe_failed rather than merging directly. Projects without the
queue keep the direct pull-request merge, which refuses MergeTargetStale when
the base moved during CI.
When an item has several active lanes, yoke merge item takes as its source
the lane whose head is the landing record's pull-request head. It verifies QA
at that commit, reruns parent-level Command cases in that lane, and keys the
merge receipt on that lane's branch. With no lane at that head it takes the
sole integration lane; otherwise it refuses, lists the lanes, and names the
recovery.
Integration-lane merge refusals
Integration-lane merges record the verified candidate's first carrying merge
through item_landings.record before removing the lane. Local fast-forwards
record the candidate itself. The same transaction releases the epic's path
claims and migration territory; delivery still retains the work claim.
A Git integration or commit-hook failure reports merge_integration_refused
with Git's diagnostic and leaves the pending integration in its lane. Fix the
named refusal, complete the pending merge commit when one exists, then retry
the same merge command. An authored-file limit refusal therefore names the
file that needs splitting instead of classifying engine-created changes as
user dirt.