agentsclimarketplace

Landing lander

Skill ottto-ai/agent-skills/skills/landing-lander

Use when the reasoning landing agent owns the PR queue and release path: hold the host-global owner lease, gather and reason over ready PRs, create and land multi-PR integration batches, resolve or defer conflicts, reconcile/close source PRs, track deploy/smoke evidence, and keep LANDING_AGENT_OWNS_QUEUE single-owner behavior safe. Also defines the always-on runner, takeover/recovery, and the rules non-owner agents must follow.From its SKILL.md

Install
npx -y skills add ottto-ai/agent-skills --skill landing-lander

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

38.3 KB, ~9.2k tokens by cl100k_base, as published. Nobody here has run it

landing-lander

Own the ready-PR to production loop. This skill is for the one active landing agent, not for ordinary PR authors. The agent is expected to reason over the queue state, use git freely in scratch/integration branches, decide what to batch, resolve easy conflicts, defer risky PRs, and keep source PR authors informed until their work is merged, deployed, or explicitly blocked.

Do not reduce this to a deterministic FIFO script. The scripts below are safety rails and state helpers. They do not replace agent judgment.

This skill must be self-sufficient. A lander invoked with only this skill and the objective "own and drain the ready queue" should know the queue contract, batching preference, auth requirements, source-PR bookkeeping, and deploy closeout behavior without needing a one-off prompt.

Why batching is the point

Every integration PR costs a full CI + queue + deploy round. Serial singletons are the single biggest source of queue latency and CI/deploy spend. The owner's success metric is: most eligible ready PRs ride the next integration, singleton integrations are recorded exceptions, and one bad PR never blocks the clean rest.

Operating Contract

  • Exactly one lander owns the queue at a time, enforced by TWO layers: the host-global owner lease (landing_owner_guard.py, one lease per repo per host, shared across all linked worktrees) for the agent session, and the process lock inside landing_lander.py for each command invocation.
  • Active queue ownership requires repository variable LANDING_AGENT_OWNS_QUEUE=1. 0, unset, or delete means stand down and let the legacy queue path work.
  • The owner intake label is ready-for-agent.
  • ready-to-land is the legacy label. During cutover, migrate current ready PRs to ready-for-agent with the label helper.
  • The main throughput objective is to collapse many clean source PRs into a small number of integration PRs. Prefer batches up to the configured max (LANDING_BATCH_MAX_SIZE, default 10) whenever risk, checks, conflicts, and deploy surface allow it.
  • One-PR integration batches are exceptions, not the steady-state goal. Use one only when the queue has a single clean source PR, no other source PR can be safely combined, or the operator explicitly asks for a singleton. Record the singleton reason in the owner log.
  • Existing batch-land:* integration PRs are queue debt to reconcile, not normal source candidates for a new batch. Be aware of them so you do not duplicate or strand source PRs, but do not let blocked singleton integrations monopolize the cycle when there are other clean ready-for-agent source PRs that can be batched.
  • Existing singleton integrations are replaceable queue debt until the Landing Queue actually owns them. If a one-PR integration is still open/unqueued and a better multi-PR batch is available, the owner may close/supersede that integration, restore its source PRs to ready-for-agent, and build one larger integration branch instead. Do this only after confirming the old integration has no active queue run and the source PR heads still match the integration body.
  • While an integration is queue-owned and merging, do not idle: prepare the next candidate batch, repair dirty sources, and reconcile deploy evidence. Check-pipelining (manual path): once the CURRENT integration is queue-owned (its queue run dispatched and past validate start), you MAY open the NEXT integration early so its 14-15m head checks run while the current one rides the queue — under ALL of these conditions: the next batch uses only sources NOT in the current one; its changed files do not overlap the current batch's (overlap = wait, or you buy a conflict rebuild); and you dispatch the next integration's OWN queue run only after the current is terminal (the queue slot stays serial). If the current merges first and master moves, the next's queue validate handles it (replacement run); rebuild only on an actual conflict. Never run two queue slots at once, and never pipeline more than one integration ahead.
  • The lander must not silently strand source PRs. Every source PR included in an integration branch needs comments/labels pointing to its replacement integration PR, merge SHA, deploy/smoke evidence, or blocker.
  • Close source PRs after their integration PR reaches master. It is acceptable for deploy evidence to be added as comments/labels after close.
  • Use batch-land, pr-landing, and release-deploy skills as needed. Re-read their SKILL.md files before active landing, merge monitoring, or deploy monitoring.

Rules For Every OTHER Agent (non-owners)

While LANDING_AGENT_OWNS_QUEUE=1:

  • Route a finished source PR to the owner ONCE with .github/scripts/pr_landing.py land --pr <n>. That applies ready-for-agent and stops. Do NOT re-invoke it in a loop, re-add labels the owner removed, or poll-with-mutation. Watch progress read-only (gh pr view).
  • Never invoke landing_lander.py --mode active, batch_land.py --apply, or deploy dispatch for queue work. Active lander commands refuse while a live owner lease belongs to another agent — do not work around that with LANDING_LANDER_SKIP_LEASE_CHECK (that seam is for tests only).
  • A source PR labeled landing-batch-active (or commented as covered by an integration PR) is the owner's; do not relabel it back to ready intake.
  • If the owner looks dead, do not start landing yourself mid-conversation: check landing_owner_guard.py status --json; if the verdict is stale, either touch <state>/owner-wake and let the ensure loop revive an owner, or run run_landing_owner.sh (with LANDING_OWNER_TAKEOVER=1 only for a stale lease). Takeover of a LIVE lease is operator-only.

Always-On Owner Runtime

The owner is a headless Claude Code session (default claude-opus-4-8, --effort high, budget-capped) spawned by the runner and revived by a launchd ensure loop. All state is host-global under the MAIN checkout: <main>/.local-state/landing-lander/ (the shared root resolves via git rev-parse --git-common-dir, so every linked worktree sees the same lease, lock, ledger, and owner log).

Components (all in .agents/skills/landing-lander/scripts/):

  • landing_owner_guard.py — the owner lease. claim (refuses a LIVE other owner; --takeover adopts a STALE one), heartbeat, status --json, release. Liveness is a bundle: lease heartbeat age, the Python lander's active-owner marker for the same agent id, the Claude transcript mtime (~/.claude/projects/*/<agent-id>.jsonl), a spawned pid (dead pid = takeable immediately), and a phase-declared --expected-wait-until window. Fresh window default 900s (LANDING_OWNER_FRESH_SECONDS), wait grace 600s.
  • run_landing_owner.sh — claim-before-spawn runner: claims the lease, prepares the dedicated workspace worktree (~/worktrees/landing-owner-workspace, detached at origin/master), renders owner_prompt.md, spawns headless claude -p with --session-id <lease id>, records the pid into the lease, waits, releases. The owner runs UNRESTRICTED by an operator decision: no spend cap, web tools and MCP available, permission prompts disabled. If the Claude spawn fails fast (non-zero exit within LANDING_OWNER_FALLBACK_WINDOW_S, default 180s — auth/quota/plan exhaustion), the runner hands the SAME lease to a Codex fallback owner (codex exec, LANDING_OWNER_CODEX_MODEL default gpt-5.5) so the queue never stalls on one vendor; owner-runs.log records which engine spawned. Knobs: LANDING_OWNER_MODEL/EFFORT/FALLBACK_MODEL.
  • Headless discipline: owners are one-shot -p sessions — the process ends when the response ends, killing background children and releasing the lease. Owners must wait in the FOREGROUND in bounded slices (re-invoking the resume-safe lander commands), never background-and-exit to "wait for a notification".
  • ensure_landing_owner.sh — the revive loop (launchd StartInterval 300s, one instance per label). Live lease → exit. Stale lease + work → spawn with takeover. No owner + work (ready labels, open batch-land/* PRs, or repair-requested repairs) → spawn. operator-hold PRs do not spawn an owner by themselves; they are parked on a human trust/scope decision. Idle → exit (this is the low-traffic light mode: no agent runs when the queue is empty, and the next labeled PR brings one up within ~5 minutes).
  • install_landing_owner_launchd.sh install|uninstall|status — manages com.example.landing-owner-ensure.

Operator controls:

  • Approve an operator-hold PR (the alert IS the approval surface — the decision is recorded on the PR, not in any agent session; owners are ephemeral, so whichever owner spawns next continues the work): click Approve & Land on the macOS hold dialog, OR run the one-liner from the Slack alert: gh pr edit <n> --add-label ready-for-agent --remove-label operator-hold (add a PR comment for any nuance/constraints — the next owner reads the thread). Rejecting = comment the decision and close the PR, or leave it on hold with a note.
  • Force a spawn now: touch <state>/owner-wake (next tick) or run .agents/skills/landing-lander/scripts/run_landing_owner.sh directly.
  • Hold everything: touch <state>/owner-disabled (and/or set the repo variable LANDING_AGENT_OWNS_QUEUE=0). Remove the file to resume.
  • Inspect: landing_owner_guard.py status --json (who owns, phase, one-line note, activity age), tail <state>/owner-runs.log (one line per claim/spawn/release with engine=claude|codex), tail <state>/owner-log.md (per-cycle packets: what landed, evidence, deferrals), tail <state>/ensure.log, raw stdout under <state>/agent-logs/.
  • Read an owner session like a conversation (reasoning + every tool call): python3 .agents/skills/landing-lander/scripts/show_owner_session.py (--list for all sessions newest-first; latest/id-prefix; -n 80; --all). This is the first-class review surface — the interactive claude --resume picker HIDES headless (-p) sessions, so owner sessions never appear there, from any directory.
  • Open one in the Claude Code UI anyway (verified): from ~/worktrees/landing-owner-workspace run claude --resume <session-id> --fork-session with an id from owner-runs.log or the viewer's --list. --fork-session keeps the historical transcript untouched. Do not continue an owner session to issue new queue work (spawn via the runner instead).
  • Alerting: repeated fast spawn failures (both engines dying within the fallback window — e.g. Claude quota exhausted AND Codex broken) raise a macOS notification at 2 consecutive failures (~10 min) and every ~30 min after, plus Slack when LANDING_QUEUE_ALERT_SLACK_WEBHOOK_URL is set for the LaunchAgent. Streak state: <state>/spawn-fast-failures.count (cleared on any healthy session). The Python lander separately Slack-alerts breaker trips and deploy failures via the same webhook.
  • Engines: primary Claude Code claude-opus-4-8 at --effort high (overload fallback claude-sonnet-5 mid-session); Codex fallback gpt-5.5 at model_reasoning_effort=high (LANDING_OWNER_CODEX_MODEL/CODEX_EFFORT).

Owner heartbeat discipline

The owner heartbeats the lease at every phase change and at least every 10 minutes of reasoning: guard heartbeat --agent-id <id> --phase <phase> --note "<one line>". Before a bounded external wait (queue run, CI, deploy), declare it: --expected-wait-until "+<seconds>"; clear with --clear-wait after. Long blocking landing_lander.py waits are covered automatically — the Python lander touches its active-owner marker every poll, and the guard counts that as activity for the same --session-id.

Takeover and recovery

Owners die (crash, budget cap, sleep, kill). Recovery contract:

  1. The ensure loop notices (stale verdict or dead pid — dead pid is takeable immediately) and spawns a successor with --takeover when work exists.
  2. The successor inherits context from three places, in order of authority: GitHub state (open integrations, labels, queue runs, deploys) > <state>/owner-log.md cycle packets + lease history (previous owner's last phase/note) > state.json (current_integration_pr, cooldowns).
  3. Reconcile before creating anything new: resume watching an open integration, re-queue it exactly once if checks settled, close out merged ones (batch_land.py --reconcile-pr), then resume batching. Never open a duplicate integration for the same sources.
  4. Every owner appends a cycle packet to <state>/owner-log.md after each cycle and a final packet before exit — that file is the cross-owner memory. Keep packets compact: backlog seen, batch built, integration PR + head SHA, queue run ids, merge SHA, deploy/smoke evidence, deferrals with reasons, next action. This is MANDATORY, not best-effort: sessions that skip packets break the next owner's inheritance (the most common owner failure observed live).
  5. The apply engine can produce a NARROWER batch than the shadow preview (a source's required checks may still be pending at apply time). Always read the cycle JSON's excluded list, record each exclusion + reason in the packet, and treat excluded-but-still-ready sources as your own next-cycle intake — they ride the next batch once their checks settle.
  6. An open integration you created or inherited keeps you on duty until it merges and its sources reconcile; exiting mid-watch forces a respawn churn cycle (~5 min gap + fresh-context cost) that a bounded foreground wait avoids.

GitHub Auth

Active owner work needs the repo-scoped fine-grained workflow token in macOS Keychain service YOUR_GITHUB_TOKEN_KEYCHAIN_SERVICE, account $USER unless LANDING_GITHUB_TOKEN_KEYCHAIN_ACCOUNT is set. Do not print the token.

Use it only for operations that need elevated repo-scoped authority, such as workflow dispatch/requeue, owner-mode labels/comments when normal gh auth is insufficient, repository variables, deploy orchestration, and workflow-file git pushes. Prefer helpers such as landing_lander.py, pr_landing.py, and release_deploy.sh; they read the approved Keychain token where needed.

If you must call gh directly with the token:

GH_TOKEN="$(security find-generic-password -a "${LANDING_GITHUB_TOKEN_KEYCHAIN_ACCOUNT:-$USER}" -s YOUR_GITHUB_TOKEN_KEYCHAIN_SERVICE -w)" \
  gh <command>

The token must be limited to selected repository <your-org>/<your-repo>. Required permissions for the full owner path: Metadata: read, Actions: read/write, Contents: read/write, Issues: read/write, Pull requests: read/write, Variables: read/write, Environments: read/write, and Workflows: read/write when changing or pushing workflow files.

If pat_readable is false in landing_lander.py status --json, or a helper reports missing/insufficient fine-grained permissions, stop with an auth blocker. Do not broaden the normal GitHub CLI OAuth token.

First Checks

Run these before active work:

python3 .agents/skills/landing-lander/scripts/landing_owner_guard.py --json status
python3 .agents/skills/landing-lander/scripts/landing_lander.py status --json
python3 .agents/skills/landing-lander/scripts/landing_lander.py host-preflight
python3 .github/scripts/landing_agent_labels.py ensure-labels --apply

status --json now includes owner_lease (verdict + holder) and shared_state_root. If the lease belongs to a live different agent, stand down. If owner_flag_enabled is false, do not mutate the queue; ask the operator to flip gh variable set LANDING_AGENT_OWNS_QUEUE --body 1 (revert with --body 0 or delete).

Cutover migration:

python3 .github/scripts/landing_agent_labels.py migrate-ready
python3 .github/scripts/landing_agent_labels.py migrate-ready --apply

The apply path refuses unless LANDING_AGENT_OWNS_QUEUE=1, unless an operator explicitly passes the helper's force flag.

While the flag is enabled, normal source PR agents that still call .github/scripts/pr_landing.py land --pr <number> are routed into this owner intake: eligible source PRs get ready-for-agent instead of ready-to-land or a regular PR-backed merge. Owner integration branches such as batch-land/integration-* are allowed to continue through merge/queue only when they also carry the owner intake label and batch-land body markers, so the lander can finish release closeout without letting source PRs spoof the integration path. Because branch names, labels, and PR bodies are PR-controlled, the integration exception is not automatic: only the active owner should pass --allow-owner-integration when intentionally landing its own integration PR. If the landing CLI cannot read LANDING_AGENT_OWNS_QUEUE, it fails closed and does not merge, queue, or relabel the PR until repository variable state is readable.

Scope Classes (what can ride the queue, and what to do instead)

The trusted Landing Queue validates changed paths SERVER-SIDE against .github/scripts/landing_policy.py. Batching cannot bypass that, and batch_land.py --include only helps for paths the queue actually accepts. Classify every candidate PR:

ClassPaths (summary)Owner action
Queue-eligiblebackend/, frontend/src|public|tests, .github/scripts/, docs/ai/kb/provider-impact/, tools/desktop-app/ app code/tests (an example project-specific allowlist widening), NON-PRODUCTION workflows frontend-ci.yml docs-kb-ci.yml zizmor.yml master-merge-notification.yml backend-ci.yml backend-full-tests.yml landing-queue-stale-label.yml (operator-approved 2026-07-05; double-keyed in NON_PRODUCTION_WORKFLOW_EXCEPTIONS + policy allowlist), top-level docs/dev/*.html + docs/efforts/*.html, and the exact allowlist files (incl. AGENTS.md, docs/efforts/current-efforts.html, generated frontend contract files)Batch normally. For macOS app code note the queue has no swift CI lane: validation is author-side swift build/test plus the docs/KB + policy lanes; when judgment warrants (large/risky app change), run swift build/swift test --package-path tools/desktop-app in your workspace before including.
Additive DB migrationgreen backend PR with additive/well-understood Alembic migrationBatchable; inspect migration + combined deploy surface. Defer only destructive/risky/unreadable ones.
Repo tooling & docs outside queue scope.agents/, .claude/, scripts/ (non-allowlisted), docs/** markdown, evals/NOT queue-eligible. Small validated operator-flow/docs fixes: land via the audited fast lane (batch_land.py --fast-lane --fast-lane-reason ... or the repo fast-lane flow) after running the paths' own validators. Otherwise defer to the author/owner lane with a precise comment and atomically replace ready-for-agent with operator-hold so intake counts stay true without respawning no-op owners. Never leave such a PR sitting in ready intake cycle after cycle.
macOS release/signing/publish actionsrelease dispatch, notarization, appcast, caskmacos-stable-release skill lane only; never batch through the queue. AGENT-RUN without per-release operator approval (operator standing approval) — do NOT park these on operator-hold; route to the release lane or a release agent.
Operator-gatedproduction workflows (the SENSITIVE_WORKFLOW_EXACT set) + all other non-excepted .github/workflows/** (incl. ci-runner-image-build.yml, oidc-role-guard.yml, video-render.yml), infra/terraform/**, secrets, prod infra, destructive migrations, landing_policy.py trust wideningsNever include without the operator's approval for the exact path. Defer atomically to operator-hold with exact risky paths named.

When a non-queue-eligible class keeps recurring, propose a landing_policy.py allowlist widening to the operator as its own reviewed change — widening the queue's trust boundary is an operator decision, not an owner default.

Migration landing lanes, explicitly:

  • Additive / well-understood (new tables, new nullable/defaulted columns, new indexes, constraint rebuilds where existing rows trivially satisfy the new grain): batch normally. Inspect upgrade() yourself — a drop_ that appears only in downgrade() is NOT destructive (marker false-positives happen; the 2026-07-02 rev-202 case is the canonical example).
  • Destructive or operationally risky (dropping/renaming used columns or tables, data rewrites, long-locking backfills, ordering-sensitive deploys): never inside a wide batch. Get the operator's approval for the exact migration, then land it as a SINGLETON integration so the deploy blast radius is isolated, watch backend deploy + smoke to terminal state before resuming batching, and record the sequencing in the owner log. If it cannot ride the queue (marker gate), the lane is an operator-approved fast-lane with the same isolation.
  • Unreadable/ambiguous: defer with the exact revision file and why, ping the author, remove it from intake until answered.

Do not treat ordinary release-bookkeeping PRs as operator-sensitive just because they mention a release: a macOS stable pin/docs PR that only updates private repo pin JSON, tests, docs/KB, or the effort board after the release passed is a normal batching candidate. The operator-gated boundary is the release/publish/signing/infra/workflow ACTION itself, plus secrets, Terraform/OIDC, production workflow edits, destructive/risky migrations, or unclear approval.

Shadow Reasoning

Use shadow mode to inspect the queue without mutation:

python3 .agents/skills/landing-lander/scripts/landing_lander.py cycle --json
python3 .agents/skills/landing-lander/scripts/landing_lander.py gather --json
python3 .agents/skills/landing-lander/scripts/landing_lander.py assemble --json --max 10

Shadow cycle is a merge preview. It gathers ready-for-agent, builds scratch candidate branches through batch_land.py --dry-run --skip-heavy --required-label ready-for-agent, reports included PRs, conflicts, excluded PRs, and does not run heavy local tests. CI validates the final integration PR when active.

Reason over the report:

  • Separate source PRs from existing integration PRs. PRs titled batch-land:* or branches named batch-land/integration-* are already integrations; monitor, merge, close, defer, or supersede them as needed, but do not include them as source PRs inside a new integration batch.
  • Confirm the proposed batch makes sense by files, risk, ownership, CI, and deployment surface.
  • For each conflict, inspect the conflict locally. Resolve generated/simple conflicts when clear; otherwise exclude the PR from this batch and comment why.
  • For outside trusted-queue scope exclusions, apply the Scope Classes table above: batch what the queue accepts, fast-lane or defer the rest with exact paths, and never recycle the same exclusion without a decision.
  • Do not treat database migrations as automatically sensitive. A green backend PR with an additive or well-understood Alembic migration can be batched; inspect it and the combined deploy surface. Defer only if the migration is destructive, operationally risky, unreadable from the validated PR head, or needs explicit operator sequencing.
  • Treat docs/efforts/current-efforts.html, docs/KB source-card/index churn, and adjacent durable docs conflicts as normal lander-owned merge work when the conflict is textual. Use the batch-land owner repair path first; if it unions both sides, commits rerere-staged generated-doc resolutions, refreshes only generated KB source-card/index artifacts, and docs/KB validation is green, keep the PR in the batch. Defer only with concrete conflict files, validation tail, and author action when the text directly contradicts, validation fails, or the conflict includes code/tests/workflows, infra, migrations, secrets, or runtime behavior that needs a real owner review. For current-efforts.html specifically: preserve both sides' new links/PR/deploy evidence, keep the latest true state per effort row, and defer only when two edits make incompatible factual claims that GitHub/CI/deploy evidence cannot decide.
  • Prefer a clean multi-PR batch over singleton landing. Prefer a smaller clean batch over a large fragile one.
  • Do not let one bad PR block the rest of the queue. Defer it, label/comment it, and keep landing clean work.

Active Cycle

After the operator flips LANDING_AGENT_OWNS_QUEUE=1 and the host preflight is clean, run:

python3 .agents/skills/landing-lander/scripts/landing_lander.py --mode active cycle \
  --session-id <lease-agent-id> \
  --session-name '<name>' \
  --session-source '<source>' \
  --max 10

--session-id must equal your owner-lease agent id, so the lease gate, the active-owner marker, and PR metadata all agree on who is acting.

Active cycle:

  1. Verifies the owner flag, owner lease, host, local lock, active-owner marker, and breaker.
  2. Gathers ready-for-agent PRs and separates source PRs from existing batch-land integration PRs.
  3. Reconciles existing integration PRs only enough to avoid duplicate work: monitor active queue runs, queue/requeue green integrations, close/deploy already-merged integrations, and defer or supersede blocked singletons with clear comments. Pending-check singleton integrations should not monopolize the cycle while other clean source PRs can be batched.
  4. Uses batch_land.py --apply --skip-heavy --fill-to-max --allow-conflicting-sources --required-label ready-for-agent to open one integration PR for the safely mergeable source set. DIRTY/CONFLICTING sources are scratch-merged instead of pre-excluded; only actual conflicts, policy gates, or validation failures are deferred. --skip-heavy skips the expensive backend/frontend lanes only; generated KB source-card/index repair and docs/KB validation still run locally so stale generated docs do not create red integration PRs. A pre-dispatch freshness gate re-checks the authoritative origin/master tip (ls-remote) AFTER assembly and BEFORE pushing: if master moved it drops any now-landed source and rebuilds on the fresh tip, so a CONFLICT_REBASE_REQUIRED queue-dispatch failure from a stale base should now be rare (it killed a 10-PR integration on 2026-07-05). The gate's decision is in the JSON output under freshness_gate (outcome = not_moved / refreshed / freshness_gate_conflict); a freshness_gate_conflict is a clean no-op (nothing pushed) — rebuild and rerun.
  5. Actively ensures that integration PR is queue-owned before and during the merge watch. If the first queue run skipped because PR-head checks were still pending, the owner reruns the canonical landing helper once checks settle instead of waiting for the full watch timeout. A watch budget expiring on a STILL-HEALTHY integration (open, checks pending, or a queue run queued/active) is not a failure: re-arm the watch or hand off with the integration OPEN. Close-and-recover only on terminal evidence (validate failed, closed unmerged, cancelled queue with red checks) — closing a healthy pending integration wastes its CI round and bounces sources back to intake. An in_progress/queued queue run is never "stale": quiet 10-15 minute gaps between its jobs (validate → push) are normal, so judge runs only by job states, never cancel a queue run or close its PR mid-run, and treat only 45+ minutes with zero job progress as an operator escalation, not a cancellation.
  6. Reconciles source PRs after merge.
  7. Deploys/monitors/smokes by merged SHA unless --no-deploy is set. Before dispatching the deploy helper, refresh origin/master and verify the merged SHA is included, then use the helper's latest-master path so trusted candidate auto-heal still works. If release-train evidence is temporarily unreadable after the bounded watch window, record deploy/smoke as deferred, do not claim last_live_sha, and continue batching while the owner session keeps monitoring/returning to the deploy evidence gap. The deploy evidence wait uses LANDING_LANDER_DEPLOY_MAX_POLLS (default 8) so long merge-watch windows do not monopolize intake.
  8. Loops until the ready backlog drains, hits max iterations, or reaches a real blocker.

Use drain only when you explicitly want the older land-only loop. cycle is the owner-facing command for the full release responsibility.

Manual Reasoning Path

When cycle output is insufficient, do the work manually in your workspace or a scratch worktree:

  1. Fetch current origin/master.
  2. Create a scratch integration branch.
  3. Select source PRs, not existing integration PRs. Skip or separately reconcile PRs titled batch-land:* or branches named batch-land/integration-*.
  4. Fetch each source PR head and verify the selected SHA still matches GitHub.
  5. Merge PR heads one at a time when the source PR will be closed by the batch. Cherry-pick or equivalent workflows are allowed only when batch_land.py can prove the source patch is already represented in the candidate tree.
  6. Resolve easy conflicts. For stale source branches, merge or rebase their head onto current origin/master in the scratch integration branch and inspect the actual conflict. If the fix is local and obvious, keep the PR in the batch via the candidate-ref path. Abort and defer PRs whose conflicts are risky or need their author.
  7. If fixes live only in the owner branch, push an explicit candidate ref such as landing-agent/batch-<id> and record its SHA. Prefer real merge commits from each source head, then conflict-resolution commits, so provenance is mechanically provable.
  8. Validate and open the integration PR with batch_land.py --candidate-ref <ref> --candidate-sha <sha> --prs "<numbers>" --required-label ready-for-agent --apply .... This keeps the exact fixed tree while the queue still validates and lands the integration PR. Candidate validation rejects final diffs with sensitive paths, destructive migration markers, or unsupported paths before any source PR labels are removed.
  9. For clean PR-head batches with no owner-only fixes, use the default batch_land.py --apply --required-label ready-for-agent ... path.
  10. Remove ready-for-agent and any legacy ready-to-land label from included source PRs only after the integration PR exists, then comment with the replacement PR.
  11. After merge, close source PRs with the integration PR and merge SHA.
  12. Monitor deploy/smoke by merged SHA; update source PRs with deploy-pending, deployed, deploy-deferred, or deploy-failed labels/comments as evidence becomes available.

This path is intentionally flexible. Use it when the agent can make a better batch than the default helper, especially for easy conflict resolution or a high-load queue.

Source PR Communication

For every source PR included in a batch:

  • Before integration PR merge: comment that the integration PR replaces the source PR, ensure it carries landing-batch-active, and remove ready-for-agent plus any legacy ready-to-land so the source does not race either queue.
  • After integration PR merge: comment Landed via <integration PR> (<sha>) and close the source PR.
  • During deployment: add or update deploy labels and comments with backend, frontend, smoke, run URLs, and the production verdict. If deploy is skipped because there is no runtime change, say that explicitly. If release-train evidence is unreadable, say deploy-deferred, include the train-status verdict/current-pointer error, and do not state the PR is live.
  • On failure: remove any active-batch ambiguity, add deploy-failed or a clear blocker comment, and either fix/delegate or return the PR to an appropriate ready/hold state.

Label vocabulary (created/maintained by landing_agent_labels.py ensure-labels):

  • ready-for-agent — source PR waiting for owner intake (ONLY sources)
  • landing-integration — owner-built integration PR riding the trusted queue (batch_land applies it at creation; the queue resolves it via landing_queue.resolve_required_label; pre-split integrations still carrying ready-for-agent keep landing during the transition)
  • repair-requested — deferred source PR whose branch repair is owned by the author session or a spawned fixer (see Deferred-PR Repair Ownership)
  • operator-hold — deferred source PR parked on a human trust/scope/sequencing decision; not a mechanical fixer job and not counted by the ensure loop as work. The operator or author re-enters it by applying ready-for-agent.
  • landing-batch-active — source covered by an open integration
  • landed-in-batch, deploy-pending, deployed, deploy-failed

Blockers And Defer Rules

Defer a PR from the current batch when:

  • Its head changed after readiness.
  • It is draft, blocked by review, failed required checks, or cannot fetch.
  • It touches operator-gated paths without explicit approval.
  • It has merge conflicts that are not clearly safe to fix in the integration branch.
  • It causes combined validation to fail and the failure is not obviously a baseline issue.

When deferring, leave a concrete PR comment: why it was deferred (files, base/ head SHAs, attempted operation), what would unblock it, which label now owns its state (repair-requested for mechanical repair, operator-hold for human policy/scope decisions), and whether the author, a spawned fixer, or the operator owns the next step. Record the deferral in the owner log so the next owner does not re-litigate it from scratch.

Deferred-PR Repair Ownership (no PR left behind)

A deferral is a handoff with a deadline, never an exit. When you defer a source PR for branch repair (conflicts vs current master, stale generated files):

  1. Change the label ATOMICALLY, repair-requested ON before ready-for-agent OFF, in ONE edit: gh pr edit <pr> --add-label repair-requested --remove-label ready-for-agent. NEVER remove ready-for-agent as a separate first step — a deferral is multi-step (label, comment, maybe spawn a fixer) and your lease can end mid-deferral (owner transitions happen), so a source left with NEITHER label is stranded: invisible to intake AND unmarked for repair. One atomic edit means the worst interruption leaves both labels briefly, never neither. Then leave the structured deferral comment (files, base/head SHAs, attempted op, exact fix needed) and record it in the owner log. A source PR must ALWAYS carry exactly one of: ready-for-agent, repair-requested, operator-hold, landing-batch-active, or be closed/merged — never zero. For operator-gated trust/scope decisions, use operator-hold instead of repair-requested; repair owners and fixers cannot make progress there, and the ensure loop intentionally ignores that label.
  2. Ping the author first: the PR body's Agent session id names the authoring session. For Claude Code sessions check ~/.claude/projects/*/<session-id>.jsonl mtime — fresh (under ~30 min) means the author is likely active and your deferral comment is the ping; give it until your next cycle or ~45 minutes.
  3. Then spawn a fixer: if the author session is inactive, unidentifiable, or the deadline passed: .agents/skills/landing-lander/scripts/spawn_pr_fixer.sh <pr> "<one-line evidence>" (headless repair agent in its own worktree; it merges master into the PR branch, resolves per the deferral evidence, validates the touched lanes, pushes, removes repair-requested, and routes the PR back to intake). Record the spawn in the owner log.
  4. You own the outcome. Every cycle, list repair-requested PRs (gh pr list --label repair-requested): repaired (head moved + MERGEABLE + checks green) → it re-enters intake and batches normally; fixer finished but the PR is still broken or silent → respawn ONCE with sharper evidence; after two failed fixer attempts → stop, comment the blocking decision on the PR, escalate to the operator in the owner log. No infinite loops.
  5. Priority is unchanged: repairs run in the background while ready PRs batch first — but a repair-requested PR older than ~6h with no active fixer is a stalled handoff you must act on this cycle.

Deployment Closeout

After an integration PR merges:

.github/scripts/pr_landing.py monitor --pr <integration-pr> --once
.agents/skills/release-deploy/scripts/release_deploy.sh train-status all <merge-sha> --json
.agents/skills/release-deploy/scripts/release_deploy.sh monitor backend <merge-sha> --once
.agents/skills/release-deploy/scripts/release_deploy.sh monitor frontend <merge-sha> --once

Treat train-status and deploy monitor output as production truth:

  • live, covered by newer, or deploy_skipped_no_runtime_change is terminal for that service.
  • Active CI/orchestrator/deploy means wait or hand off with exact run URLs.
  • Missing candidate with no active owner means dispatch the normal deploy helper for latest master (release_deploy.sh all or the service-specific helper), not backend/frontend deploy workflows directly.
  • Failed deploy/smoke means fix, delegate, or trip the breaker.

Do not report "fully QAed" from default smoke. Report exactly what was checked.

Safety Limits

  • Never edit or push master directly for normal owner work.
  • Never force-push, reset, or rebase shared worktrees.
  • Never keep two active lander hosts armed.
  • Never let the deterministic helper's FIFO selection override obvious agent reasoning about conflicts, risk, deploy surface, or author ownership.
  • Never use the deploy PAT as the normal gh OAuth reader unless a helper explicitly requires it. The fine-grained PAT is for workflow dispatch, repo variables, workflow-file git push, and deploy authority.
  • Never take over a LIVE lease without the operator; --force-takeover is an operator emergency tool.

Tests

python3 .agents/skills/landing-lander/scripts/tests/test_landing_lander.py
python3 .agents/skills/landing-lander/scripts/tests/test_landing_owner_guard.py
python3 .agents/skills/batch-land/scripts/tests/test_batch_land.py

What ships with it: 19 files

482.0 KB alongside SKILL.md, 14 of them executable

Gives 0 of the 12 instructions most ship operate skills give in ~9.2k tokens

Counted across 779 of the 1,178 authors here whose files we hold, read 2026-08-07

  • Document a rollback plan before deploymentin 41 of 779, across 22 files
  • Update the changelogin 21 of 779, across 19 files
  • Run the test suitein 20 of 779
  • Create an annotated git tagin 20 of 779
  • Clean up feature flags after full rolloutin 18 of 779, across 10 files
  • Verify deployment health after launchin 18 of 779, across 10 files
  • Test both feature flag statesin 17 of 779, across 9 files
  • Verify the working tree is cleanin 17 of 779
  • Make database migrations backward-compatiblein 16 of 779, across 8 files
  • Set up error monitoring before launchin 15 of 779, across 7 files
  • Monitor metrics at each rollout stagein 14 of 779, across 5 files
  • Create a GitHub releasein 14 of 779

Said here and by no other author read

  • hold the host-global owner lease
  • reason over ready pull requests
  • create multi-pull-request integration batches
  • resolve easy merge conflicts
  • defer risky pull requests
  • inform source authors until merge or block

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

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.