Files
octopus-workflow/core/skills/land-batch/SKILL.md
T

10 KiB

name, description, stage, triggers, role
name description stage triggers role
land-batch Use ONLY when the orchestrator lands the finished task branches of one DAG iteration as a single batch PR ([org-internal #3731] batch-landing pilot). Composes path-disjoint member branches via the deterministic batch-compose script, pre-validates locally, opens ONE PR for N tasks (one CI fan-out instead of N on the single-runner pool), pastes each member's review synthesis into the PR conversation, runs one delta-scoped review-code on the batch head, then hands the labeled PR to the merge coordinator. NOT for hotfixes / CI-infra fixes (fast lane: 1:1 PR), single tasks, or single-session mono-branch batches (topology A — one session owns every ticket on one branch from inception; hand-compose those, see Applicability). merge-pr
land batch
batch land
batch-compose
批量归并
批量提交 pr
Producer

land-batch — batch PR composition for one DAG iteration

Orchestrator-only. The composing session is the epic's orchestrator (single writer at iteration close — the same role that already owns uniform merge/close). workflow/batch/* branches are NOT claim branches: no claim.sh interaction, no assignee; the orchestrator is the only composer. Pilot charter: the decision comment on [org-internal #3731] (scope, opt-in protocol, cap, success criteria) — this skill implements that charter, not the epic's frozen DAG v1 (which covers only the merge-coordinator nodes N-01..04).

Applicability: post-hoc composition only (topology B)

Two batch shapes exist ([org-internal #3731] charter addendum, comment 39347):

  • Topology A — single-session mono-branch batch: one session owns every ticket from inception and writes them on ONE branch (instance: PR [org-internal #3861], audit-h1). Hand composition is natural there — do NOT force this skill on it; record the outcome on [org-internal #3731] as a manual-batch datum instead.
  • Topology B — parallel workers, post-hoc composition: workers finish on SEPARATE branches at different times; the orchestrator composes at iteration close. This is the skill's ONLY applicability — cross-branch conflict attribution, lockfile convergence, and composition ordering are exactly the error-prone parts it mechanizes (path-disjoint admission, fail-closed attribution, --convergent surgery, per-member commit retention for revert granularity).

Quick discriminator: if every "member" would be a commit on a branch you are already writing, that is topology A — keep going, skip this skill. If you are landing OTHER workers' branches you never owned, that is topology B — this skill applies.

Preconditions (all mandatory, fail-closed)

  1. Pilot opt-in: the epic runs in batch-landing mode — its workers were seeded to report status=done branch=<ref> WITHOUT opening PRs (see the pilot note in headless-session-ops/reference/completion-report.md). Non-pilot epics keep the per-task 1:1 PR flow.
  2. Every member is done: review-code PASS on its own branch, run bundle archived (gen-run-bundle.ts --slug <slug> --ticket <N> — the bundle is committed on the member branch, so composition carries it; the merge-gate's presence check is per close-ref and expects it).
  3. 2..7 members — a single done task rides its own 1:1 PR; more than 7 (batch-compose --max-members, the pilot cap) splits into multiple batches along the path-overlap report.
  4. No fast-lane cargo: prod hotfixes and CI-infra fixes bypass batching entirely (1:1 PR, land immediately) — batching latency must never gate urgent work.

Procedure

1. Compose (deterministic core)

bun .gitea/scripts/batch-compose.ts \
  --branch workflow/task/<a> --branch workflow/task/<b> … \
  --out workflow/batch/<epic-slug>-iter<N> --dry-run

For batches whose members ALL rewrite a regenerable shared file — the rule, not the exception, for dependency-bump batches (every bun install rewrites the root bun.lock) — pass --convergent bun.lock: the file is exempt from the overlap admission, and a merge that conflicts ONLY on it is surgically resolved to the running head's version (dropped from the index outright when the head no longer carries the path), with the JSON reporting converged: ["bun.lock"] and a regeneration hint. A conflict report carrying shapes the tool cannot attribute (rename/delete etc.) fails CLOSED — convergent exemption never widens into a blind pass.

Exit codes / handling: 1 usage / no-changes-over-base → drop the named member or fix the invocation; 3 path-overlap → split the batch along the reported pairs (or land the overlapping member 1:1) and re-run per group; 4 merge conflict / 5 transport/git error → fall back to 1:1 for this batch; 0 → drop --dry-run and push the composed branch. The script never touches the worktree or index (keeper-style plumbing) and each member keeps its original commits — revert granularity stays per-task.

2. Pre-validate locally (never enter CI red)

git fetch origin workflow/batch/<slug>
git switch --detach origin/workflow/batch/<slug>
cd packages/octopus && bun run test:changed
cd ../.. && git switch -   # back to the orchestrator branch

If the compose output carried a regenerate convergent files (…) hint — regenerate them FIRST, on the detached batch branch, and push the result (the composed branch deliberately carries a stale convergent file: either the surgically-resolved running-head version, or a member's clean-merged one; either way it is deterministic only after regeneration):

git switch --detach origin/workflow/batch/<slug>
bun install                                        # regenerates bun.lock
git add bun.lock                                   # NEVER `commit -am` here: -a stages the
                                                   # worktree's symlink stubs and trips the
                                                   # pre-commit symlink guard (core.symlinks=false)
git commit -m "[<epic-slug>][iter-N] chore(deps): regenerate bun.lock for batch <slug>"
git push origin HEAD:refs/heads/workflow/batch/<slug>

(On detached HEAD the commit-msg hook's format check is inert — the [slug][iter-N] prefix above keeps the corpus contract anyway.)

Failure → fix on the offending member branch (it is the only place the fault can live — paths are disjoint), re-compose, re-validate. Do NOT push a known-red batch.

3. Open the batch PR (gitea-rest recipes)

Title: [<epic-slug>][iter-N] batch: <task-a> / <task-b> / …

Body constraints (each is a merge-gate requirement, not style):

  • English close keywords, one per member issue: Closes #A, Closes #B, … — Gitea auto-closes all of them; Chinese 关闭/修复 never auto-close.
  • Zero unchecked top-level - [ ] lines in the body — the gate's regex (^-\s+\[ \]) blocks on any unindented unchecked box, with no fence stripping. Render deferred/pending items as plain text (· deferred: …), never as checkboxes; the per-task live checklists stay on their issues.
  • Per-member summary (ticket, branch, one line) + bundle index.

4. Paste member syntheses into the PR conversation

Post one comment per member carrying its review-code verdict block (**Verdict**: PASS|WARN + the fenced JSON synthesis, from the member issue's review thread). Why: the merge-gate hunts FAIL verdicts only in the PR's own comment stream — a batch PR without these comments passes vacuously (a fake-green hole). Pasting restores FAIL visibility; a member synthesis of FAIL blocks the gate exactly as a 1:1 PR's would.

5. One review-code pass on the batch head

Run review-code against the batch PR. Members already PASS'd individually — scope the review delta to what composition added (merge seams, cross-member integration the per-task reviews could not see). This posts the required per-sha pipeline/review-code status; member statuses do NOT transfer to the composed head.

6. Label and hand off

Label the PR ready-to-merge. From here the flow is stock: the merge coordinator's double-green queue serially merges (CI/check + pipeline/merge-gate on the batch head, bot approve, main-account merge). Do NOT hand-merge, do NOT hand-sync main into the batch branch.

7. Post-merge expectations

Member issues auto-close via the English keywords; their ## 当前状态 PR/代码评审/CI rows fan out to EVERY close ref (status-sync poller Wide behavior — requires [org-internal #3867], merged 2026-08-31). task done ≡ PR merged still holds, transitively through the batch PR; verify milestones fire unchanged.

Fallback matrix

Failure Action
path-overlap (exit 3) split batch along overlap pairs / 1:1 for the odd member
merge conflict (exit 4) 1:1 for the whole batch
local pre-validation red fix on the offending member, re-compose
batch CI red after mergeable coordinator dequeues with trail; fix on member branches, re-compose as a new batch PR

Any fallback preserves today's behavior — batch landing is an optimization, never a dependency.

Measurement (pilot duty — revised per [org-internal #3731] charter addendum 39347)

Per-iteration attribution only; the global 7-day ratio is trend context, never a pass/fail judge (same-window confounders: test-slim [org-internal #3855] / test-dedupe [org-internal #3854] / the pilot's own PRs). On the server, bun .gitea/scripts/ci-run-stats.ts --days 7 --json /tmp/<slug>-ci-stats.json (the --json flag takes a filename — bare, it is a silent no-op; the human summary still prints). Same methodology as the checklist-merge-gate.yml 2026-08-27 note. Record on [org-internal #3731]: (a) this batch iteration's per-workflow trigger counts vs a historically similar 1:1 iteration, and (b) one fallback drill (construct a path-overlap group → exit-3 split executed). Baseline, trend only: 2026-08-31, 5,081 runs / TOTAL 2.75x single runner.