/ship-loop — Bot-paired fast lane PR cycle
Lane choice: use this skill for coherent-arc internal PRs — one closable bead, or one small-epic slice (≤5 child beads of the same surface), with paired tests. The PR is the atomic-revert unit: bundle scenarios that ship-or-revert together; split scenarios with independent rollback. For fork-based OSS contributions, use the /pr-* family (pr-research, plan, pr-implement, etc.; tier contribute). For large epics (15+ child beads) or multi-wave work, use /crank. See CLAUDE.md ## Workflow for the canonical unit-of-PR rule.
Capture of the discipline that landed 8/9 internal PRs in the 2026-05-18 session at 19.5-min median time-to-merge. Five named failure modes (F1–F5); four closed mechanically. The full rationale lives in docs/learnings/2026-05-18-xp-bdd-tdd-workflow-synthesis.md.
Overview / When to Use
Run this skill at the START of each PR you intend to ship to your own main branch. The skill enforces the cycle as a sequence; each step has a clear done-state and gate.
Pair partner: claude-review (GitHub App workflow at .github/workflows/claude.yml) auto-fires on pull_request: opened/synchronize. No @claude mention is required. Operator does the edits; bot does the review check.
The 9-step cycle
- Claim.
bd ready → pick the highest-severity unblocked item, OR read .agents/rpi/next-work.jsonl for harvested follow-ups. bd update <id> --claim atomically.
- Branch off fresh main.
git checkout main && git pull --rebase. Then git checkout -b <type>/<slug>-<bead-id>. NEVER stack off a sibling branch; auto-merge handles serialization via update-branch.
- Write the FIRST FAILING TEST. BDD scenario (Gherkin) for behavior; unit test for invariants. The test must fail for the right reason (asserting expected behavior, not just "doesn't crash"). See references/test-shape.md.
- Minimal implementation. Smallest code change that makes the test green. Resist scope creep. Refer to the project's standards (
.claude/rules/{go,python}.md).
scripts/ship.sh (recommended) — auto-detects inventory-touching changes and runs the regen sweep (sync-skill-counts, codex-hashes, domain-map, context-map, registry, sync-hooks) preemptively. This is the mechanical fix for anti-pattern #1 — removes the operator's choice to skip the rule. CI (.github/workflows/validate.yml) is the sole authoritative push gate per docs/contracts/local-pre-push-gate-retirement.md (soc-g2r9, PR #357); the previous scripts/pre-push-gate.sh local mirror was retired because per-incident drift cost dominated the per-push wait. For per-tool sanity before push, run only what your diff touches: cd cli && make test, bats tests/scripts/<file>.bats, scripts/regen-codex-hashes.sh. If a pre-existing blocker appears in unchanged-from-base content (visible in CI now), file an atomic side-quest fix PR first — don't bundle. See references/anti-patterns.md. For PRs that change gate/validator/CI behavior: capture the targeted output line and include it verbatim in the PR body as Evidence:; the validate-pr-evidence-claims CI job (scripts/verify-gate-claim.sh, soc-o5kq + soc-eqjd) verifies each Evidence line against the workflow run's logs and blocks the PR if any claim is absent (mechanical enforcement of anti-pattern #7).
- Commit with conventional-commit scope.
feat(<scope>):, fix(<scope>):, docs(<scope>):. Body explains the failure mode the test reproduces and how the fix removes it.
- Push +
gh pr create. Body cites the bead, the validation results, and links to the learning anchor in the script body (NOT a .agents/learnings/ file existence — that breaks in CI's fresh clone).
gh pr merge <num> --squash --auto. Immediately. The bot fires claude-review automatically on PR open. When all required checks pass, merge fires without operator action.
- Close the bead.
bd close <id> --reason "Merged via PR #<num>". The coherent-arc rule should keep concurrent PR count low (typically 1-2); when a large-epic split puts multiple PRs in flight against the same main, invoke scripts/gh-merge-chain.sh on the chain.
Gate sequence (what each enforces)
| Gate |
Enforces |
| Per-tool local checks (optional) |
cd cli && make test for Go; bats tests/scripts/<file>.bats for shell; scripts/regen-codex-hashes.sh for codex parity. Run only what your diff touches. |
claude-review (auto on PR open) |
Reviewer pair — the bot half |
.github/workflows/validate.yml |
Sole authoritative push gate (soc-g2r9, PR #357). 60+ job suite on PR head: cli-docs-parity, embedded-sync, skill-frontmatter, registry-check, security-toolchain, validate-pr-evidence-claims (AP#7), plus the F-mode closures |
gh pr merge --squash --auto |
Auto-merge when all required checks pass |
scripts/gh-merge-chain.sh (optional) |
Chain N PRs through auto-merge with update-branch on each successor when a predecessor merges (closes F3) |
Failure-mode mapping
| ID |
Failure |
Mechanical guard |
| F1 |
Script rewrite leaves dead variables; --fast shellcheck misses them |
Unconditional shellcheck on staged .sh (PR #326) |
| F2 |
Pre-existing blocker compounds across concurrent branches |
Open. Rule: fix as an atomic side-quest PR FIRST; don't bundle. See references/anti-patterns.md. |
| F3 |
gh pr merge --auto doesn't auto-rebase BEHIND branches |
scripts/gh-merge-chain.sh (PR #329) |
| F4 |
Bot trigger doc claimed mention-only; actual trigger is auto on PR open |
Doc corrected (PR #327) |
| F5 |
Stale ~/.config/evolve/KILL silently blocks /evolve |
EVOLVE_KILL_TTL_DAYS=7 auto-expire (PR #328) |
| meta |
Tests assert local-only file existence; fail in CI |
grep -q '<slug>' "$SCRIPT" instead of [ -f .agents/learnings/<x>.md ]. See references/test-shape.md. |
Anti-patterns
Read references/anti-patterns.md for the full list with examples. Headline anti-patterns:
- Running
--fast pre-push on an inventory-touching PR — new skill, contract, or schema → use FULL gate; --fast skips ~15 inventory validators
- Bundling pre-existing fixes — file each as its own atomic PR
- Keeping copied variables after a rewrite — after a script rewrite, the first self-check is "are all variable declarations used?"
- Asserting local-only state in CI tests — grep the reference, don't check the file
- Branches off out-of-date main —
git checkout main && git pull --rebase at branch creation
- Skipping the failing-test-first step — adding a test after the fix gives false confidence
Session scope (sister rule to coherent-arc)
Coherent-arc governs the shape of a single PR; session-scope governs the count of consecutive PRs in an autonomous session.
- Default: 2-4 PRs per autonomous session. Both arcs ship cleanly and merge.
- ≥5 PRs in flight or merged in one session triggers a mandatory post-mortem before continuing. Diminishing returns and reactive-PR spirals (PR-fixes-fallout-from-prior-PR) are the dominant failure mode in the back-half of long sessions.
- Post-mortem shape (1-2 sentences each): Which PRs were planned vs reactive? How many self-corrections? Was the marginal PR discovery or churn?
Derivation: the 2026-05-19 cron-loop session shipped 6 PRs with 3 self-corrections; PRs #5–#6 each fixed fallout from #1–3. Visible reactivity by PR #5; the cron-loop kept nudging "keep going" without surfacing the post-mortem signal. Mechanical enforcement ships as hooks/session-pr-counter.sh — a PreToolUse Bash hook on gh pr create that fires at count >= threshold-1 (default 5) and emits the post-mortem prompts via additionalContext, with optional hard-block via AGENTOPS_SESSION_PR_BLOCK=1 (soc-1aou, PR #362). (soc-waxr)
Pair mechanics (claude-review)
claude-review fires automatically on pull_request: opened and synchronize. No @claude mention required.
- If
claude-review is IN_PROGRESS, wait — don't poke. The bot does NOT respond to its own comments (anti-loop protection).
- If
claude-review is silent after PR open, the workflow may need permission upgrades (see docs/contracts/claude-bot-delegation.md Gotchas 1-4) — surface to operator, do not retry.
- If you hit the self-revert loop (PR #270 case — bot reverting its own forward-port of
claude.yml), rebase the branch locally onto fresh main and force-push.
Examples
Closing a harvested next-work item:
1. /post-mortem ran; .agents/rpi/next-work.jsonl has an unclaimed "medium" item
2. /ship-loop picks the item: branch fix/<slug>-<bead> off main
3. Write the failing test that proves the failure mode exists
4. Add the minimal fix
5. Pre-push --fast → green
6. Push → gh pr create → gh pr merge --squash --auto
7. claude-review auto-runs; validate.yml runs; auto-merge fires
8. bd close <id>
Shipping a chain of PRs:
1-9. Run the cycle for each PR (off main, not stacked)
10. After all PRs are open with auto-merge enabled:
scripts/gh-merge-chain.sh <pr1> <pr2> <pr3>
11. Helper polls + update-branches each successor as the predecessor merges
See references/examples.md for full walkthroughs.
Troubleshooting
| Problem |
Cause |
Solution |
| Auto-merge stalls |
claude-review IN_PROGRESS or branch BEHIND |
Wait for review; if BEHIND, gh api repos/<o>/<r>/pulls/<n>/update-branch -X PUT or use gh-merge-chain.sh |
claude-review never fires |
Workflow lacks trigger or perms |
Check .github/workflows/claude.yml on: block and permissions; may require workflows: write upgrade |
| Pre-push --fast blocks on unchanged content |
Pre-existing F2-class blocker |
File the fix as an atomic side-quest PR first; rebase your branch onto the side-quest's merge |
| Self-revert loop on a stale branch |
Bot reverting its own forward-port |
Rebase locally onto fresh main; force-push with --force-with-lease |
| Test asserts local file in CI |
.agents/ is gitignored |
Change to grep -q '<slug>' "$SCRIPT" (reference assertion, not file existence) |
See Also
- pr-implement — fork-based OSS contribution (different tier; different use case)
- crank — multi-wave epic execution
- rpi — full lifecycle orchestrator (ship-loop is the per-PR mechanics inside RPI's implementation phase)
- post-mortem — harvests next-work items that ship-loop consumes
- beads — task tracker that drives the claim step
References
Reference Documents
- references/ship-loop.feature — Executable spec: claim→test→impl→push→squash-merge→close, one coherent arc, gated merge + bead close (soc-qk4b)
1---2name: ship-loop3description: Bot-paired fast-lane cycle for coherent-arc internal PRs (one closable bead or small-epic slice): claim → test → impl → pre-push → push → squash auto-merge → close.4---5
6# /ship-loop — Bot-paired fast lane PR cycle
7
8> **Lane choice:** use this skill for **coherent-arc internal PRs** — one closable bead, or one small-epic slice (≤5 child beads of the same surface), with paired tests. The PR is the *atomic-revert unit*: bundle scenarios that ship-or-revert together; split scenarios with independent rollback. For fork-based OSS contributions, use the `/pr-*` family (`pr-research`, `plan`, `pr-implement`, etc.; tier `contribute`). For large epics (15+ child beads) or multi-wave work, use `/crank`. See `CLAUDE.md ## Workflow` for the canonical unit-of-PR rule.
9
10Capture of the discipline that landed 8/9 internal PRs in the 2026-05-18 session at 19.5-min median time-to-merge. Five named failure modes (F1–F5); four closed mechanically. The full rationale lives in [`docs/learnings/2026-05-18-xp-bdd-tdd-workflow-synthesis.md`](https://github.com/boshu2/agentops/blob/main/docs/learnings/2026-05-18-xp-bdd-tdd-workflow-synthesis.md).
11
12## Overview / When to Use
13
14Run this skill at the START of each PR you intend to ship to your own `main` branch. The skill enforces the cycle as a sequence; each step has a clear done-state and gate.
15
16**Pair partner:** `claude-review` (GitHub App workflow at `.github/workflows/claude.yml`) auto-fires on `pull_request: opened/synchronize`. No `@claude` mention is required. Operator does the edits; bot does the review check.
17
18## The 9-step cycle
19
201. **Claim.** `bd ready` → pick the highest-severity unblocked item, OR read `.agents/rpi/next-work.jsonl` for harvested follow-ups. **`bd update <id> --claim`** atomically.
212. **Branch off fresh main.** `git checkout main && git pull --rebase`. Then `git checkout -b <type>/<slug>-<bead-id>`. NEVER stack off a sibling branch; auto-merge handles serialization via update-branch.
223. **Write the FIRST FAILING TEST.** BDD scenario (Gherkin) for behavior; unit test for invariants. The test must fail for the *right reason* (asserting expected behavior, not just "doesn't crash"). See [references/test-shape.md](references/test-shape.md).
234. **Minimal implementation.** Smallest code change that makes the test green. Resist scope creep. Refer to the project's standards (`.claude/rules/{go,python}.md`).
245. **`scripts/ship.sh`** (recommended) — auto-detects inventory-touching changes and runs the regen sweep (sync-skill-counts, codex-hashes, domain-map, context-map, registry, sync-hooks) preemptively. **This is the mechanical fix for anti-pattern #1** — removes the operator's choice to skip the rule. CI (`.github/workflows/validate.yml`) is the sole authoritative push gate per `docs/contracts/local-pre-push-gate-retirement.md` (soc-g2r9, PR #357); the previous `scripts/pre-push-gate.sh` local mirror was retired because per-incident drift cost dominated the per-push wait. For per-tool sanity before push, run only what your diff touches: `cd cli && make test`, `bats tests/scripts/<file>.bats`, `scripts/regen-codex-hashes.sh`. If a pre-existing blocker appears in unchanged-from-base content (visible in CI now), **file an atomic side-quest fix PR first** — don't bundle. See [references/anti-patterns.md](references/anti-patterns.md). **For PRs that change gate/validator/CI behavior**: capture the targeted output line and include it verbatim in the PR body as `Evidence:`; the `validate-pr-evidence-claims` CI job (`scripts/verify-gate-claim.sh`, soc-o5kq + soc-eqjd) verifies each Evidence line against the workflow run's logs and blocks the PR if any claim is absent (mechanical enforcement of anti-pattern #7).
256. **Commit with conventional-commit scope.** `feat(<scope>):`, `fix(<scope>):`, `docs(<scope>):`. Body explains the failure mode the test reproduces and how the fix removes it.
267. **Push + `gh pr create`.** Body cites the bead, the validation results, and links to the learning anchor in the script body (NOT a `.agents/learnings/` file existence — that breaks in CI's fresh clone).
278. **`gh pr merge <num> --squash --auto`.** Immediately. The bot fires `claude-review` automatically on PR open. When all required checks pass, merge fires without operator action.
289. **Close the bead.** `bd close <id> --reason "Merged via PR #<num>"`. The coherent-arc rule should keep concurrent PR count low (typically 1-2); when a large-epic split puts multiple PRs in flight against the same main, invoke [`scripts/gh-merge-chain.sh`](references/gh-merge-chain.md) on the chain.
29
30## Gate sequence (what each enforces)
31
32| Gate | Enforces |
33|---|---|
34| Per-tool local checks (optional) | `cd cli && make test` for Go; `bats tests/scripts/<file>.bats` for shell; `scripts/regen-codex-hashes.sh` for codex parity. Run only what your diff touches. |
35| `claude-review` (auto on PR open) | Reviewer pair — the bot half |
36| `.github/workflows/validate.yml` | **Sole authoritative push gate** (soc-g2r9, PR #357). 60+ job suite on PR head: cli-docs-parity, embedded-sync, skill-frontmatter, registry-check, security-toolchain, validate-pr-evidence-claims (AP#7), plus the F-mode closures |
37| `gh pr merge --squash --auto` | Auto-merge when all required checks pass |
38| `scripts/gh-merge-chain.sh` (optional) | Chain N PRs through auto-merge with `update-branch` on each successor when a predecessor merges (closes F3) |
39
40## Failure-mode mapping
41
42| ID | Failure | Mechanical guard |
43|---|---|---|
44| **F1** | Script rewrite leaves dead variables; `--fast` shellcheck misses them | Unconditional shellcheck on staged `.sh` (PR #326) |
45| **F2** | Pre-existing blocker compounds across concurrent branches | **Open.** Rule: fix as an atomic side-quest PR FIRST; don't bundle. See [references/anti-patterns.md](references/anti-patterns.md). |
46| **F3** | `gh pr merge --auto` doesn't auto-rebase BEHIND branches | `scripts/gh-merge-chain.sh` (PR #329) |
47| **F4** | Bot trigger doc claimed mention-only; actual trigger is auto on PR open | Doc corrected (PR #327) |
48| **F5** | Stale `~/.config/evolve/KILL` silently blocks /evolve | `EVOLVE_KILL_TTL_DAYS=7` auto-expire (PR #328) |
49| **meta** | Tests assert local-only file existence; fail in CI | `grep -q '<slug>' "$SCRIPT"` instead of `[ -f .agents/learnings/<x>.md ]`. See [references/test-shape.md](references/test-shape.md). |
50
51## Anti-patterns
52
53Read [references/anti-patterns.md](references/anti-patterns.md) for the full list with examples. Headline anti-patterns:
54
551. **Running `--fast` pre-push on an inventory-touching PR** — new skill, contract, or schema → use FULL gate; `--fast` skips ~15 inventory validators
562. **Bundling pre-existing fixes** — file each as its own atomic PR
573. **Keeping copied variables after a rewrite** — after a script rewrite, the first self-check is "are all variable declarations used?"
584. **Asserting local-only state in CI tests** — grep the reference, don't check the file
595. **Branches off out-of-date main** — `git checkout main && git pull --rebase` at branch creation
606. **Skipping the failing-test-first step** — adding a test after the fix gives false confidence
61
62## Session scope (sister rule to coherent-arc)
63
64Coherent-arc governs the *shape* of a single PR; session-scope governs the *count* of consecutive PRs in an autonomous session.
65
66- **Default: 2-4 PRs per autonomous session.** Both arcs ship cleanly and merge.
67- **≥5 PRs in flight or merged in one session triggers a mandatory post-mortem before continuing.** Diminishing returns and reactive-PR spirals (PR-fixes-fallout-from-prior-PR) are the dominant failure mode in the back-half of long sessions.
68- **Post-mortem shape (1-2 sentences each):** Which PRs were planned vs reactive? How many self-corrections? Was the marginal PR discovery or churn?
69
70**Derivation:** the 2026-05-19 cron-loop session shipped 6 PRs with 3 self-corrections; PRs #5–#6 each fixed fallout from #1–3. Visible reactivity by PR #5; the cron-loop kept nudging "keep going" without surfacing the post-mortem signal. Mechanical enforcement ships as `hooks/session-pr-counter.sh` — a PreToolUse Bash hook on `gh pr create` that fires at `count >= threshold-1` (default 5) and emits the post-mortem prompts via `additionalContext`, with optional hard-block via `AGENTOPS_SESSION_PR_BLOCK=1` (soc-1aou, PR #362). (soc-waxr)
71
72## Pair mechanics (claude-review)
73
74- `claude-review` fires automatically on `pull_request: opened` and `synchronize`. No `@claude` mention required.
75- If `claude-review` is `IN_PROGRESS`, wait — don't poke. The bot does NOT respond to its own comments (anti-loop protection).
76- If `claude-review` is silent after PR open, the workflow may need permission upgrades (see `docs/contracts/claude-bot-delegation.md` Gotchas 1-4) — surface to operator, do not retry.
77- If you hit the self-revert loop (PR #270 case — bot reverting its own forward-port of `claude.yml`), rebase the branch locally onto fresh main and force-push.
78
79## Examples
80
81**Closing a harvested next-work item:**
82
83```
841. /post-mortem ran; .agents/rpi/next-work.jsonl has an unclaimed "medium" item
852. /ship-loop picks the item: branch fix/<slug>-<bead> off main
863. Write the failing test that proves the failure mode exists
874. Add the minimal fix
885. Pre-push --fast → green
896. Push → gh pr create → gh pr merge --squash --auto
907. claude-review auto-runs; validate.yml runs; auto-merge fires
918. bd close <id>
92```
93
94**Shipping a chain of PRs:**
95
96```
971-9. Run the cycle for each PR (off main, not stacked)
9810. After all PRs are open with auto-merge enabled:
99 scripts/gh-merge-chain.sh <pr1> <pr2> <pr3>
10011. Helper polls + update-branches each successor as the predecessor merges
101```
102
103See [references/examples.md](references/examples.md) for full walkthroughs.
104
105## Troubleshooting
106
107| Problem | Cause | Solution |
108|---------|-------|----------|
109| Auto-merge stalls | `claude-review` IN_PROGRESS or branch BEHIND | Wait for review; if BEHIND, `gh api repos/<o>/<r>/pulls/<n>/update-branch -X PUT` or use `gh-merge-chain.sh` |
110| `claude-review` never fires | Workflow lacks trigger or perms | Check `.github/workflows/claude.yml` `on:` block and permissions; may require `workflows: write` upgrade |
111| Pre-push --fast blocks on unchanged content | Pre-existing F2-class blocker | File the fix as an atomic side-quest PR first; rebase your branch onto the side-quest's merge |
112| Self-revert loop on a stale branch | Bot reverting its own forward-port | Rebase locally onto fresh main; force-push with `--force-with-lease` |
113| Test asserts local file in CI | `.agents/` is gitignored | Change to `grep -q '<slug>' "$SCRIPT"` (reference assertion, not file existence) |
114
115## See Also
116
117- [pr-implement](../pr-implement/SKILL.md) — fork-based OSS contribution (different tier; different use case)
118- [crank](../crank/SKILL.md) — multi-wave epic execution
119- [rpi](../rpi/SKILL.md) — full lifecycle orchestrator (ship-loop is the per-PR mechanics inside RPI's implementation phase)
120- [post-mortem](../post-mortem/SKILL.md) — harvests next-work items that ship-loop consumes
121- [beads](../beads/SKILL.md) — task tracker that drives the claim step
122
123## References
124
125- [references/anti-patterns.md](references/anti-patterns.md)
126- [references/examples.md](references/examples.md)
127- [references/gh-merge-chain.md](references/gh-merge-chain.md)
128- [references/test-shape.md](references/test-shape.md)
129- Durable rationale: [docs/learnings/2026-05-18-xp-bdd-tdd-workflow-synthesis.md](https://github.com/boshu2/agentops/blob/main/docs/learnings/2026-05-18-xp-bdd-tdd-workflow-synthesis.md)
130
131## Reference Documents
132
133- [references/ship-loop.feature](references/ship-loop.feature) — Executable spec: claim→test→impl→push→squash-merge→close, one coherent arc, gated merge + bead close (soc-qk4b)