Skip to content
Back to skills

Factory Ship

ASecurity

Release — open a develop → master PR, wait for CI, merge, verify the deploy

  • 15 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 2, 2026
ai-agentsrustgobashgitapidatabase

Works with

  • api

Security analysis

A100/100

Scanned September 20, 2026

npx -y skills add watt-mind/factory --skill factory-ship --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Factory Ship?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Factory Ship
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/watt-mind-factory-ship/badge)](https://www.skillsdirectory.com/skills/watt-mind-factory-ship)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: factory-ship
description: Release — open a develop → master PR, wait for CI, merge, verify the deploy
---

# factory-ship

The user's accompanying request is this workflow's argument string. Wherever these instructions refer to `$ARGUMENTS`, interpret it as that request.

Ship the current release: merge everything on the integration branch to the deploy branch. My invoking this command **is** the human `master` decision — the one call the autonomous loop never makes on its own. That means you carry it through to a verified deploy or a clear stop; don't hand it back to me half-done.

Interpret `$ARGUMENTS` as a repo name from `config/repos.yaml` (run from that repo's checkout); default is the repo you're standing in.

## 1. Pre-flight — run the command, don't re-derive it

**Start here, always:**

```bash
factory ship-preflight --repo <name>      # read-only; exit 0 means shippable
```

It prints one PASS / FAIL / SKIP line per check and, on every FAIL, the exact command that fixes it. Do not proceed past a FAIL, and do not substitute your own reading of `gh run list` for it — this command exists because an agent spent ~2.5 h re-deriving these checks from `ci.yml` and memory (WM-1104). What it asserts, and why:

- **The base tip is fully green for the exact SHA** — every job of the CI workflow on the newest run _for that commit_, not "the latest run on the branch". A newer push cancels the previous run, so a stale run reads as red; a skipped job (`deploy-prod` never runs on develop) is not a failure; only jobs listed in the repo's `advisory_jobs` are allowed to be red.
- **Every runtime pin is fresh** — for each role in the repo's `runtime_roles`, the pinned source commit is an ancestor of the tip _and_ nothing under that publisher's own `on.push.paths` has moved since. A stale pin means the release would ship source the runtime image does not contain.
- **No publisher is mid-flight and no bot pin PR is open** — either means the pins are about to move under you.
- **No `escalated` PR targets the base** — that one is a human's decision, not a release's.
- **The promotion journal is clean** (`ssh_probe`) — a non-empty `active/` means a publish transaction, possibly _another repo's_, is still open; promoting over it strands the release.

Branches still come from `config/repos.yaml`: `base` (integration, usually `develop`) and `deploy_branch` (usually `master`). No entry or no `deploy_branch`: use the repo's actual branches; if base and deploy are the same branch, there is no release flow here — say so and stop. **Nothing to ship?** `git log origin/master..origin/develop --oneline` empty means done — report and stop. A repo with a dev smoke check (`smoke_workflow`/`smoke_url`) should have that green too. Shipping a red develop just promotes the breakage: fix or revert there first, which is a stop-and-notify, not something to push through.

## 1b. The publish → reconcile → pin chain

When pre-flight fails **only** on stale runtime pins, the fix is a fixed sequence — and it is what `factory ship-chain --repo <name> --until pin` drives (dry run by default; `--apply` acts):

1. `gh workflow run <publisher> --ref <base> -f commit_sha=<tip>` — **one publisher at a time** where the repo sets `serial_publishers: true`. They share one promotion lock on the runner; two at once leaves an unprepared directory under the promotion journal and every later publish exits non-zero until an operator discards it.
2. Wait for that publisher run to finish. Its receipt — the provenance artifact naming the reviewed commit, the publisher run and the registry digest — is what the next step verifies; nothing here is taken on trust from the tag.
3. Wait for `reconcile-runtime-images.yml` to open the bot's `chore(runtime): adopt reviewed runtime images` PR (it also runs on a poll, so this is a wait, not a second dispatch).
4. Merge that PR **only when its head commit's check runs are all success or skipped**. Read the check-run summary; a watched workflow run can exit 0 while another check on the same head is red, which is exactly how a red PR got merged once.
5. That merge moves the tip, so the base needs a fully green run again — go back to pre-flight. Only when it exits 0 do you open the release PR.

A **fully** green base is required (not just "deploy and smoke green") because the publishers themselves refuse to build from a commit whose exact-SHA CI did not succeed; shipping past a partial green just moves the failure to the release PR.

## 2. Mid-flight work

Open PRs targeting develop are fine — they ride the next release. But if a merge landed on develop in the last few minutes and its CI hasn't finished, wait for that run rather than shipping an unverified tip.

## 3. Open the release PR

- Reuse an existing open develop → master PR if one exists (`gh pr list --base master --head develop`) — don't stack a second.
- Otherwise create it. Title: `release: develop → master (<date>)`. Body: the commit list since last release (`git log origin/master..origin/develop --oneline --no-merges`) with Linear ticket IDs pulled out into their own line each, so the Linear integration links every shipped ticket. Do **not** put `Fixes <ID>` in the body — these tickets are already `Done`; a release PR references, it doesn't close.

## 4. Wait for CI — properly

For the release head SHA, select the CI workflow with `gh run list --workflow ci.yml --commit <sha> --json databaseId --limit 1`, wait with `gh run watch <run-id> --exit-status --interval 60`, then assert every check run completed green with `gh api repos/<owner>/<repo>/commits/<sha>/check-runs`. The workflow run can lag the push, so retry the workflow-selected lookup for up to about two minutes when it is empty. **Never `sleep` and re-poll.** Also confirm the PR is mergeable (no conflicts — a develop → master PR with conflicts means someone committed to master directly; surface that, don't resolve it silently by picking sides).

If CI is red: this is release CI on code that was already green on develop, so first look whether the failure is environmental/flaky (re-run once via `gh run rerun --failed`). A real failure means develop and master CI disagree — fix on **develop** (max 2 fix rounds, per the standard loop), let it flow back into this PR. Still red after that: stop, notify (`CI RED`), report what was tried.

## 5. Merge

**Always a merge commit: `gh pr merge <PR> --merge`.** Never squash or rebase a release PR — squashing develop into master makes every subsequent release PR re-show already-shipped commits as conflicts, and it wrecks `git log master..develop` as the ship-list source of truth. If the repo blocks merge commits, stop and tell me rather than squashing.

**No `--delete-branch` on this PR.** Its head is `develop` — the flag would delete the integration branch, and there is no branch protection to stop it (floor: **Protected branches**). Do not force anything.

## 6. Verify the deploy

The deploy branch usually auto-deploys, so the merge is not the finish line:

- Watch the post-merge run on master to completion (`gh run watch <run> --exit-status`).
- Where the repo has a prod smoke check or `smoke_url`, confirm it's green/responding after the deploy settles.
- Run the repo's `post_release_checks` from `config/repos.yaml` — they are the verification trio an operator would otherwise remember by hand (health endpoint, deployment inventory, sidecar verify). `factory ship-chain --repo <name> --until post-release` runs exactly those against the live deploy and nothing else (no pre-flight, no PR, no merge), exits non-zero on the first red one, and with `--apply` also pushes a `SMOKE RED` notification. Use it here whether the merge was done by hand or by `--until release --apply`, which runs the same checks at the end of the chain.
- **Red master CI or red smoke = live outage**: revert the release merge (`git revert -m 1 <merge-sha>` on master, push), notify immediately (`SMOKE RED` / `CI RED` via `factory notify`), and file the cause to Linear. Don't leave a broken deploy standing while investigating.

## 7. Report

What shipped: ticket IDs and one-liners, the release PR link, CI + smoke status on master. If anything was reverted, fixed, or skipped, say exactly what and why. File anything discovered along the way (flaky release CI, direct-to-master commits, missing smoke coverage) to Linear `Triage` per the discovered-work rule.

**Session friction:** skip when `FACTORY_RUN_ID` is set. When unset, scan the session per `/factory-friction` and note friction items filed (IDs) or **none observed**.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.