gh-batch-runner · diff
git:20260919.20d77cb to git:20260919.2a16c51
6 added, 3 removed. Audit A to A.
---
name: gh-batch-runner
description: "Run several planned GitHub Issues as one unit: collect them under an Epic Issue with sub-issues, implement each on a shared epic/ branch in dependency order, verify the branch as a whole, and open a single Pull Request into the integration branch. Use when the user wants several Issues fixed, verified and released together. Triggers include requests such as Issue1,2,3をまとめて対応して / まとめて修正してリリースしたい / この3件を一緒に出して, 'batch these issues', 'do issues 1 2 3 together', 'ship these issues as one release'. Prerequisite: every member Issue needs an agreed plan comment from gh-issue-planner. For a single Issue, or for Issues that ship separately, use gh-issue-resolver instead."
---
# GitHub Batch Runner
## Overview
Implement several Issues as one release: one integration branch, one verification of
the whole, one Pull Request. Per-Issue implementation follows the `gh-issue-resolver`
contract unchanged — this skill owns only what is batch-level: membership, ordering,
whole-branch verification, and the single PR.
Use this skill only when the answer to **"do these ship together?"** is yes. Independent
Issues use `gh-issue-resolver` once per Issue; a dependency chain that ships
separately uses the stacked flow (also `gh-issue-resolver`, with the dependency's
branch as base).
## Prerequisites
- **Every member Issue has an agreed plan comment** (`<!-- gh-issue-planner:agreed-plan -->`)
posted by `gh-issue-planner`. If any Issue lacks one, stop and run the planner for
that Issue first — planning several Issues in one pass is supported there.
- The repository's **integration branch** is known (CLAUDE.md `## Flow`; falls back to the
default branch).
## Workflow
### Step 1: Collect the batch under an Epic Issue
The batch's membership is an Epic Issue holding the members as sub-issues — not a branch
name, not a comment convention. Create the Epic if the user did not name one:
```bash
gh issue create --title "Epic: <what ships together>" --label epic --body "$(cat <<'EOF'
## まとめて対応する Issue
<one line per member Issue>
## 統合先
<integration branch>
---
<!-- gh-batch-runner:batch-plan -->
*Generated by `gh-batch-runner`. Membership is the sub-issue list; edit that, not this body.*
EOF
)"
```
- Then attach each member (the API takes the issue's **id**, not its number):
+ Then attach each member. Reading takes the Issue **number**; writing takes the REST
+ **integer id**, which is not the same thing as the `I_kwDO…` node id that
+ `gh issue view` reports under `id` — the endpoint rejects that with HTTP 422. Use `-F`
+ rather than `-f` so the value stays an integer instead of becoming a string:
```bash
- sub_id=$(gh issue view <member-number> --json id --jq .id)
- gh api -X POST repos/{owner}/{repo}/issues/<epic-number>/sub_issues -f sub_issue_id="$sub_id"
+ sub_id=$(gh api repos/{owner}/{repo}/issues/<member-number> --jq .id)
+ gh api -X POST repos/{owner}/{repo}/issues/<epic-number>/sub_issues -F sub_issue_id="$sub_id"
```
### Step 2: Re-read dependencies and order the work
Read the **current** state every run — dependencies are recorded on Issues at any time,
including after the batch started:
```bash
gh api repos/{owner}/{repo}/issues/<epic-number>/sub_issues --jq '.[].number'
gh api repos/{owner}/{repo}/issues/<member>/dependencies/blocked_by --jq '.[].number'
```
Order the members so every blocker lands before what it blocks. If the dependencies form
a cycle, stop and report it — do not pick an order arbitrarily.
### Step 3: Create the epic branch
```bash
git branch epic/<epic-number>-<slug> <integration-branch>
git worktree add ../epic-<epic-number>-<slug> epic/<epic-number>-<slug>
```
The epic branch is a staging area, not a protected branch. Branch protection stays on the
integration branch, and that is where the gate is.
### Step 4: Implement each Issue on the epic branch
For each member Issue, in the Step 2 order, follow `gh-issue-resolver` Steps 3–4 with:
- `<base-ref>` = the epic branch
- **no child PR** — the change lands as one commit per Issue:
`git commit -m "<type>(#<member>): <short description>"`
- the Issue's own agreed plan as the scope wall, exactly as in the individual flow
- per-Issue verification as usual, with the epic branch as the baseline
A member whose verification cannot converge stops the batch at that member (Step 7) —
later members are not started on top of an unverified commit.
### Step 5: Verify the epic branch as a whole
Per-Issue verification does not cover what the combination broke. Re-run the full
verification once more against the **integration branch** as baseline:
1. Full test suite on the epic branch.
2. Re-run every diagnosis whose triggers fire for the *combined* diff
(`gh-issue-resolver` 8.1 table, applied to `git diff <integration-branch>...HEAD`).
3. Classify findings against the integration branch:
- **regression of the batch** — passes on the integration branch, fails on the epic
branch → fix it here, under the usual limits (≤3 iterations, inside the union of the
members' agreed 影響範囲)
- **pre-existing** — already fails on the integration branch → hand to
`report-to-issues` after the user approves; never fixed in this PR
4. A regression that cannot be attributed to a single member is still the batch's: fix it
in the member whose 影響範囲 covers it, or stop (Step 7) if none does.
### Step 6: Open one PR into the integration branch
```bash
gh pr create --base <integration-branch> --head epic/<epic-number>-<slug> \
--title "epic(#<epic-number>): <what ships together>" --body "$(cat <<'EOF'
## Summary
<what ships together and why it ships as one>
Closes #<epic-number>
Closes #<member-1>
Closes #<member-2>
## Issues in this batch
- #<member-1> — <one line>
- #<member-2> — <one line>
## Verification
- Per Issue: <PASS/FAIL summary>
- Epic branch vs <integration-branch>: <full test suite + diagnoses re-run>
- Batch regressions fixed: <ids, or none>
EOF
)"
```
This PR is the only place CI and review run for the batch. Do not weaken it to compensate
for the epic branch being unprotected.
### Step 7: Stop conditions
Stop, leave the branch and the Epic in place, and report — never merge past a failure:
- A member has no agreed plan → run `gh-issue-planner` for it.
- A member's verification does not converge in 3 iterations → return to
`gh-issue-planner` for that member (the rest of the batch stays unstarted).
- A fix needs to leave the union of the members' 影響範囲 → return to the planner.
- A dependency discovered mid-run changes the order or the flow → record it on the Issue
first (`dependencies/blocked_by`), then either continue with the new order or stop and
say why.
- The dependency graph has a cycle → report the cycle.
### Step 8: Report completion
```
✅ Batch #<epic-number> implemented and verified.
Members: #12 #15 #18 | Epic branch: epic/<n>-<slug> | PR: <url>
Tests: pass | Batch regressions fixed: 1 | Pre-existing handed off: 2
```
## Key Principles
- **Membership lives in sub-issues**, not in branch names or comment text; re-read it, and
the dependencies, at the start of every run
- **No child PRs.** One commit per Issue, one PR per batch — the integration PR is the gate
- **Verify twice**: per Issue against the epic branch, then the epic branch against the
integration branch. Neither replaces the other
- **Batch regressions are measured against the integration branch.** What already fails
there is pre-existing and goes to `report-to-issues` — never into this PR
- The per-Issue limits are unchanged: regressions only, ≤3 iterations, inside the agreed
影響範囲. This skill adds a batch level, it does not relax the Issue level
- Never relax tests, types, or thresholds to make the batch green