agentsclimarketplace

Batch land

Skill ottto-ai/agent-skills/skills/batch-land

Use when a deep ready-to-land or ready-for-agent PR backlog is draining too slowly through the serialized Landing Queue and you want to collapse N PRs into ONE validated integration PR, or when the landing agent has built an exact owner candidate ref/sha for conflict-repaired batch landing. Dry-run first; never bypasses or races the queue.From its SKILL.md

Install
npx -y skills add ottto-ai/agent-skills --skill batch-land

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

24.0 KB, ~6.1k tokens by cl100k_base, as published. Nobody here has run it

batch-land

Collapse a deep ready-to-land or owner-mode ready-for-agent PR backlog into one validated integration PR, draining N PRs through a single trusted-queue slot. It can either build the integration tree itself from source PR heads or validate an owner-built candidate ref/sha that carries conflict fixes.

When to use this

The repo's trusted Landing Queue is serialized: it lands one PR at a time. On a busy day, agents produce dozens of ready-to-land PRs and the queue lands them one-by-one over hours. If a single queue run hangs, everything behind it wedges (the failure mode that motivated this skill: one 3h hang wedged 17 PRs).

batch-land is the durable drain. It builds one integration branch off the current origin/master, merges each eligible PR head into it, validates the combined tree with the same validators the queue runs, writes a manifest, and (only with --apply) opens a single integration PR that rides the existing trusted queue. N PRs then land through one queue slot.

Use it when:

  • there is a deep ready-to-land backlog (say 8+ green, trusted-queue-safe PRs);
  • the queue is making slow serial progress or recovering from a wedge;
  • you want one reviewable, validated artifact instead of N separate landings.

Do not use it to bypass review, to land operator-gated infra/workflow changes, or to "go faster" on a small backlog the queue will clear in minutes.

Why batch-land drains a backlog the queue can't

This is the crux. The Landing Queue serializes on a concurrency lock (concurrency: group: landing-queue-master, cancel-in-progress: false). At any instant exactly one queue run is in_progress — actually fetching master, merging its PR, pushing. Every other dispatched run is pending/queued, serialized behind that lock, often for hours, and frequently stale (a pending run whose PR was already landed or fast-laned out of band simply no-ops when it finally gets the lock).

The queue's own ACTIVE_QUEUE_STATUSES ({queued, in_progress, pending, requested, waiting}) answers "work the queue will eventually do". batch-land needs the narrower question "which PR is being merged right now", because a PR that is only sitting in a pending run is exactly the stuck backlog batch-land exists to drain. So batch-land protects only the in_progress run (MERGING_QUEUE_STATUSES = {"in_progress"}), plus a small FIFO head+next buffer as a race margin in legacy ready-to-land mode. In owner-mode ready-for-agent, the reasoning lander is the serial owner, so there is no FIFO head+next buffer; otherwise a small backlog can strand forever. The dozens of pending PRs remain batchable — when the batch lands them, their stale pending runs no-op. (Reusing the queue's broad set here would over-protect and exclude the entire backlog, defeating the tool.)

What it does NOT do

  • It does not modify, patch, or race the Landing Queue. Queue code is Codex's lane. batch-land only consumes the shared landing policy (landing_policy) and the queue's own validator selection (landing_queue), so its combined-tree gate is byte-identical to what the queue would run.
  • It does not touch the PR being merged right now (the single in_progress queue run). In legacy ready-to-land mode it also preserves the FIFO head+next race buffer. In owner-mode ready-for-agent, there is no FIFO buffer because the lander is the serial owner. A PR merely sitting in a pending/queued run IS batchable (see above).
  • It never force-pushes and never resets or rebases master.
  • Candidate-ref mode never pushes directly to master: it validates the exact owner candidate tree, rewrites only the commit shape for queue validators, and opens an integration PR that still rides the queue.
  • Combined-tree validation runs PR-controlled code, so it uses the queue's token-stripped candidate_env() (no GH_TOKEN / AWS creds in the validation environment) — a malicious ready-to-land PR cannot exfiltrate operator secrets during dry-run or apply.

Safety model (non-negotiable)

  • --dry-run is the default. Dry-run integrates + validates + writes a manifest entirely in a scratch worktree. It never pushes, opens a PR, applies a label, or mutates any PR.
  • Exclude-don't-block, log everything. A PR that conflicts, fails checks, is out of scope, touches an operator-gated infra/workflow path, or breaks the combined tree is EXCLUDED with a logged reason and surfaced in the manifest. One bad apple never sinks the batch. There are no silent caps: --max truncation and queue head/next protection are logged exclusions, not hidden drops.
  • Idempotent / resumable. Already-landed PRs are skipped. Re-running after a partial batch re-selects only what is still open and unlanded.
  • Worktree rule. All integration work happens in a dedicated scratch git worktree under the repo's ignored .local-state/batch-land/ tree, never the main checkout and never master.
  • No red PRs. It never opens an integration PR on a tree whose combined validation is not green. The bisect first drops the offending PR(s) and opens a PR for the MAX green subset (the breakers fall back to the serial queue). If NO green subset survives, the --apply run is a clean no-op: it lands nothing and exits 0 (reported as red_no_op in the JSON), NOT a failure -- the serial queue drains those PRs. A nonzero --apply exit means a genuine engine error (a git/gh fault), never "the tree was red". This is what keeps the scheduled drainer -- which fires on every queue completion -- from storm-failing on a red backlog and false-tripping the liveness watchdog.

Invocation

The engine is .agents/skills/batch-land/scripts/batch_land.py (Python 3, run with the repo's uv/python3). Run it from the repo root (or pass --repo-root).

# 1) DRY RUN (default): see what would be batched, validate the combined tree,
#    write the manifest. No push, no PR.
python3 .agents/skills/batch-land/scripts/batch_land.py --dry-run

# Smoke / fast dry run: skip the heavy backend/frontend lanes, but still run
# docs/KB plus lane-ownership + CI-policy guards (manifest flags skipped lanes).
python3 .agents/skills/batch-land/scripts/batch_land.py --dry-run --skip-heavy

# Restrict to specific PRs. --prs still enforces ALL the same gates as the
# default path -- including the required `ready-to-land` label, head/next
# protection, scope, sensitivity, and checks. It cannot batch an unapproved PR.
python3 .agents/skills/batch-land/scripts/batch_land.py --prs "1620,1621,1623"

# Cap the batch size (oldest first; the rest are logged as deferred):
python3 .agents/skills/batch-land/scripts/batch_land.py --max 8

# Include PRs whose paths are outside trusted-queue scope (operator-gated
# infra/workflow paths are STILL excluded). Use only when you understand the
# combined scope:
python3 .agents/skills/batch-land/scripts/batch_land.py --include

# 2) APPLY: after a green combined-tree validation, open the integration PR and
#    apply ready-to-land so it rides the trusted queue. Requires agent metadata.
python3 .agents/skills/batch-land/scripts/batch_land.py --apply \
  --session-id <claude-session-uuid> \
  --session-name '<terminal title / aiTitle>' \
  --session-source '<output of agent_metadata_doctor>'

# Owner candidate branch mode: use when the reasoning lander has pushed an exact
# conflict-repaired candidate ref. The command verifies current master, source PR
# heads, candidate ref -> SHA, that each source patch is represented in the
# candidate tree, and that the final candidate diff is queue-safe, then opens the
# integration PR. Requires explicit --prs for provenance.
python3 .agents/skills/batch-land/scripts/batch_land.py --apply \
  --prs "1620,1621" \
  --required-label ready-for-agent \
  --candidate-ref landing-agent/batch-20260630-01 \
  --candidate-sha <40-hex-sha> \
  --session-id <session-uuid> \
  --session-name '<name>' \
  --session-source '<source>'

# Owner dirty-source repair mode: use only under LANDING_AGENT_OWNS_QUEUE with
# the owner intake label. DIRTY/CONFLICTING PRs are allowed past SELECT so the
# scratch integration merge can prove whether they actually merge, auto-resolve
# generated/union conflicts, or need a concrete deferral.
python3 .agents/skills/batch-land/scripts/batch_land.py --apply \
  --required-label ready-for-agent \
  --allow-conflicting-sources \
  --session-id <session-uuid> \
  --session-name '<name>' \
  --session-source '<source>'

# JSON output for orchestration:
python3 .agents/skills/batch-land/scripts/batch_land.py --dry-run --json

# 3) RECONCILE (after the integration PR MERGES): close the source PRs.
python3 .agents/skills/batch-land/scripts/batch_land.py --reconcile-pr <integration-pr>

--session-* can also come from BATCH_LAND_SESSION_ID, BATCH_LAND_SESSION_NAME, BATCH_LAND_SESSION_SOURCE env vars. They are required for --apply because the integration PR is agent-authored and the queue enforces agent metadata. Resolve them via .github/scripts/agent_metadata_doctor.py --agent claude-code --session-id <id> --session-name <name> and the session-id rules in .agents/skills/repo-task-lifecycle/SKILL.md.

Operator workflow (dry-run -> review -> apply)

  1. Dry-run. Run --dry-run (add --skip-heavy for a fast first look). Read the printed summary and the manifest at .local-state/batch-land/<runlabel>.md.
  2. Review. Confirm the included set is what you expect, the excluded set has sensible reasons, anything flagged [HUMAN] is genuinely human-only (operator-gated infra/workflow paths, destructive migration markers, hold labels), and the combined-tree validation is green.
  3. Apply. If green and reviewed, rerun with --apply and the agent metadata. It pushes the integration branch, opens one PR titled batch-land: N PRs (#A #B ...) with the manifest as the body, applies ready-to-land to it, and then removes ready-to-land from the source PRs (so they step out of the serialized queue, in that order so a failure never orphans them). The trusted queue then merges that single PR.
  4. Land. Follow the integration PR through the queue with pr-landing (.github/scripts/pr_landing.py land --pr <n> / pr_watch_until_merged.py).
  5. Reconcile (post-merge). batch-land does NOT block waiting on the queue, so once the integration PR has MERGED, run the reconcile step to close the source PRs:
    python3 .agents/skills/batch-land/scripts/batch_land.py --reconcile-pr <integration-pr>
    
    It is idempotent: it refuses unless the integration PR is merged, derives the source PRs from the integration PR body's Closes (on merge): #A #B ... line, and skips already-closed ones, closing each with Landed via batch-land #<n> (<sha>). (The orchestrator can run this automatically after detecting the merge.)

The algorithm

  1. SELECT — default: all open ready-to-land PRs (or --prs). The required ready-to-land label is enforced on EVERY path (including --prs), so an unapproved PR can never be folded in and wrapped with the label. EXCLUDE, logging each reason:
    • (a) the PR(s) a queue run is merging right now (the single in_progress run, single OR batch [pr:A,B] title). In legacy ready-to-land mode, also protect a FIFO head+next race buffer (the 2 oldest ready-to-land). In owner-mode ready-for-agent, do not reserve a FIFO buffer because the lander owns serialization. This is intentionally narrower than the queue's active set — pending/queued runs do NOT protect their PRs (see "Why batch-land drains a backlog the queue can't");
    • (b) non-green required blocker checks (landing_policy);
    • (b2) PRs the queue itself would not land — conflicting with master (CONFLICTING/DIRTY) or held by branch-protection / required review (BLOCKED, REVIEW_REQUIRED, CHANGES_REQUESTED) — mirroring the queue's validate_pr refusals so the approval boundary holds for every source PR;
    • (c) paths outside trusted-queue-eligible prefixes (TRUSTED_QUEUE_ALLOWED_PREFIXES) unless --include;
    • (d) sensitive paths (infra/terraform, OIDC, production workflows) and destructive migration markers in migration upgrade() bodies -> excluded and flagged for a human. Paired index rebuilds and paired unique-constraint rebuilds in upgrade() are not destructive by marker alone; unmatched drops, non-unique constraint drops, table/column drops, truncates, deletes, and raw destructive markers remain human-gated;
    • (e) labels needs-review / hold / do-not-merge.
    • (f) PRs missing the required ready-to-land label (enforced on every path, including --prs, so an unapproved PR can never be folded in and relabeled);
    • (g) agent-authored PRs missing Agent session id/name/source (reusing the queue's agent_metadata_blockers), so the per-source audit requirement the queue enforces is not bypassed by folding them into the integration PR;
    • (h) PRs whose ready-to-land label is unauthorized or stale — reusing the queue's verify_ready_label_actor (label applied by a non-writer, or the head changed after the label was applied). Fail-closed: an events-API read failure excludes rather than assuming authorized.
  2. INTEGRATE — fetch, branch off the current origin/master.
  3. MERGE — each candidate in FIFO order. First fetch the PR's pull/<n>/head (every time) and verify the head. A head that cannot be fetched is excluded as head unavailablefail-closed even if a local object with the selected SHA exists, because a transient fetch failure cannot prove the PR's current head still equals the validated SHA (merging a stale local SHA and later closing the source as landed would be unsafe). A head that has advanced since selection (current pull/<n>/head != the SHA whose checks were validated) is excluded as head moved. Both are distinct from conflict and never silently dropped. Then git merge --no-ff <head sha> to stack each PR. On CONFLICT: attempt a safe auto-resolve for generated/union-attributed files (.gitattributes merge=union/keep-generated). In owner mode (--allow-conflicting-sources with ready-for-agent), also attempt a three-way union repair for high-churn textual docs/KB/effort-board files, especially docs/efforts/current-efforts.html; code, tests, workflow, infra, migration, and runtime conflicts remain exclusions unless an owner builds an explicit reviewed candidate ref. Otherwise git merge --abort and EXCLUDE that PR (reason conflict), then continue. If git reports a nonzero merge but rerere already staged a complete generated-doc resolution and no unmerged paths remain, commit that staged merge and keep the PR. After stacking, refresh generated KB source cards/indexes in the scratch candidate when the combined diff includes Markdown; the repair may change only docs/ai/kb/sources/ artifacts and preserves existing source-card ingest dates unless a card is actually stale. Then collapse the per-PR merges into ONE merge commit whose first parent is origin/master (remaining parents = the PR heads, for attribution). This gives the queue's single-candidate shape so HEAD^1 == base: validators that compute changed paths from HEAD^/HEAD^1 (e.g. test_backend_ci_blocker.sh) see the FULL combined change, not just the last PR (a stack of merge commits would point HEAD^1 at the previous batch commit and under-validate). Owner candidate-ref mode: when --candidate-ref/--candidate-sha are supplied, batch-land fetches that ref, requires it to resolve to the exact advertised SHA, requires it to be based on current origin/master, verifies every explicit source PR head still matches GitHub, and requires every source PR to be represented in the candidate tree before it can later be closed. Representation is proven either by source-head ancestry (the preferred path: actually merge the source PR and resolve conflicts in the owner branch) or by a clean git merge-tree no-op against the candidate tree. It also checks the final candidate diff for sensitive paths, destructive migration markers, and unsupported paths before opening the integration PR or stepping source labels. Then batch-land rewrites only the commit shape: the new integration commit has the candidate tree unchanged, current master as first parent, source PR heads plus the candidate SHA as additional parents. This preserves the exact conflict-fixed tree while keeping queue validators reliable.
  4. VALIDATE the COMBINED tree — using the queue's own lane selection (landing_queue.blocker_lane_labels -> candidate_blocker_commands): the backend blocker, frontend blocker, docs/KB, and CI-policy lanes the changed paths imply — the same commands the queue runs on that tree. If the full set is green (the common case) it is one build. On FAILURE -> greedy forward construction: rebuild from empty, adding candidates one at a time in FIFO order and keeping each only if the set stays green; a PR that turns it red is dropped (broke combined validation). This drops only true breakers — for ANY number of independent breakers — and never excludes an innocent PR, in O(n) builds. Exclusions include the failing lane and a short validation tail so authors get concrete evidence such as stale source-card paths. Exclusions come only from the final accepted build (no speculative-trial leakage), so a PR can never be both included and excluded. NEVER opens a PR on a red tree. When NO green subset survives, --apply is a clean no-op (lands nothing, exits 0, red_no_op in the JSON) and the serial queue drains the PRs -- it is not a failure. Lane ownership (scripts/lane_check.py) is computed on the final tree and recorded as a non-gating advisory in the manifest — a batch legitimately transports Codex-/shared-owned PRs through one queue slot, so cross-lane ownership is expected and never blocks landing.
  5. MANIFEST — write .local-state/batch-land/<runlabel>.md (included table #/title/author + excluded + reasons + validation results); reuse it as the integration PR body.
  6. OPEN — one integration PR batch-land: N PRs (#A #B ...) with the manifest + agent-metadata bullets; apply ready-to-land. --fast-lane (ff-only, no force, --fast-lane-reason required) is taken only when explicitly passed and operator-approved.
  7. STEP SOURCES OUT OF THE QUEUEafter the integration PR/commit exists as the replacement, remove ready-to-land from each included source PR (with a note) so their already-queued runs do not land one-by-one ahead of (or racing) it. Done in this order so a push/create/label failure never orphans the sources (they keep their queue slot until the replacement is real). Each removal is verified; any that fail to step out are surfaced as a warning (a possible racer) — not a wedge, since the replacement is already queued/landed. The sources stay open and are closed in step 8.
  8. RECONCILE (separate post-merge mode) — batch-land does not block on the queue. Once the integration PR has MERGED, run batch_land.py --reconcile-pr <n> to close each included source PR with Landed via batch-land #<n> (<sha>). Idempotent; derives the sources from the integration PR body's Closes (on merge): ... line. (The --fast-lane path has no integration PR, so it closes the sources directly during apply.)

Steps 2-5 always run (dry-run included, locally). Steps 6-7 require --apply; step 8 is the separate --reconcile-pr mode run after the PR-path merge (fast-lane closes sources inline).

Fast lane

--fast-lane (with --apply) does an ff-only push of the validated integration commit straight to master (no force, no reset). It requires --fast-lane-reason "<why>": before the push it stacks a metadata-carrying commit on the validated tip so the commit that lands on master carries the repo-required audit footer (Agent / session id / name / source + Fast lane reason) — that footer cannot be added after a direct-master push without rewriting history. It re-checks that origin/master has not moved since the branch was built; if it moved, it refuses rather than forcing. This is the documented repo-task-lifecycle direct-master fast lane and is appropriate only with explicit operator approval (e.g. unblocking a wedged queue under time pressure). Default and preferred is the PR + trusted queue path (no --fast-lane).

python3 .agents/skills/batch-land/scripts/batch_land.py --apply --fast-lane \
  --fast-lane-reason "unblock wedged queue: 17 PRs stuck 3h behind a hung run" \
  --session-id <uuid> --session-name '<name>' --session-source '<source>'

Worked example

A backlog of 15 ready-to-land PRs, the oldest two already owned by / next for the queue:

$ python3 .agents/skills/batch-land/scripts/batch_land.py --dry-run --skip-heavy
batch-land [dry-run] run 20260628-141500
base origin/master: bb3170d27b...
included: 9  excluded: 6
INCLUDED:
  + #1608 test: flag stats client budget evidence gaps (@dev)
  + #1614 Clarify Claude Code quota source gap (@dev)
  ...
EXCLUDED:
  - #1617 ...: protected: in-flight queue work -- Landing Queue run is merging it now
  - #1587 ...: protected: in-flight queue work -- FIFO queue head/next-in-line buffer
  - #1598 ...: protected: in-flight queue work -- FIFO queue head/next-in-line buffer
  - #1609 ...: conflict -- merge conflict against the integration branch
  - #1623 ...: sensitive path -- .github/workflows/backend-deploy.yml [HUMAN]
  ...
VALIDATION (combined tree):
  SKIP backend blocker
  PASS CI policy
ADVISORIES (non-gating):
  2 combined-tree file(s) belong to a Codex/shared-owned lane (expected for a
  multi-agent batch; landing not gated on this):
    BLOCKED  backend/app/features/pricing/x.py -> lane 'pricing' owned by codex
combined-tree green: True
manifest: .local-state/batch-land/20260628-141500.md

Review the manifest, then:

$ python3 .agents/skills/batch-land/scripts/batch_land.py --apply \
    --session-id 13ea164c-... --session-name 'orchestrator loop' \
    --session-source 'local transcript'
... integration PR: #1640

Then land #1640 through the queue with the pr-landing skill.

Reuse / boundaries

batch_land.py reuses, rather than re-implements:

  • landing_policy — operator-gated sensitive-path, trusted-queue-scope, and expected blocker-check predicates (shared with the queue);
  • landing_state — the PullRequest shape and check helpers;
  • landing_queueblocker_lane_labels / candidate_blocker_commands and the DEFAULT_*_BLOCKER_COMMAND constants, so the combined-tree validation matches the queue's.

This means batch-land can never select a tree the queue would reject for scope/sensitivity, and never validate it more loosely than the queue. If the queue's policy changes, batch-land inherits it automatically.

Tests

.agents/skills/batch-land/scripts/tests/test_batch_land.py covers the selection/exclusion rules, real-git conflict-exclusion (hard conflict + generated-union auto-resolve), bisect-on-failure, and manifest generation. Run:

python3 .agents/skills/batch-land/scripts/tests/test_batch_land.py   # standalone
# or
cd backend && uv run --with pytest pytest \
  ../.agents/skills/batch-land/scripts/tests/test_batch_land.py -q

See docs/dev/2026-06-28-batch-land-skill.md for the design rationale and the first smoke-test run.

What ships with it: 7 files

405.2 KB alongside SKILL.md, 6 of them executable

Keep looking

Skills are one crate of 326,614. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.