| name | ship-loop-pr |
| description | Push + open PR child loop. Pre-conditions all five inner layers (build, test, sanity, watch, smoke) green AND staged/uncommitted local changes detected. Refuses to push from main branch. On non-fast-forward push rejection, rebases on origin and retries once. Records PR URL into ship-loop-state.json. Use standalone via /loop ship-loop-pr or wired into the master orchestrator. |
| type | child-loop |
| context | persistent |
ship-loop-pr — push + open PR child loop
Activation
- Standalone:
/loop ship-loop-pr (manual push from CEO)
- Orchestrated: invoked by
ship-loop-master when:
- All five inner layers
green (build, test, sanity, watch, smoke)
git status --short shows staged or unstaged changes (something to push)
pr.state === 'not_opened'
Pre-flight (refuse to run if any fail)
git rev-parse --abbrev-ref HEAD — branch name. If main or master, refuse with ## P0 — ship-loop-pr refused: branch is main and exit
git status --short — if empty, write {"reason":"clean_tree","skipped":true} and return without changing PR state
gh auth status — must be authenticated
- Read state file — confirm all five inner layers
green; refuse if any red
Process
Step 1: Stage + commit if needed
If git status --short shows unstaged changes:
- Stage only files relevant to current work (refuse
git add -A to avoid grabbing accidental scratchpad files; use explicit paths from git diff --name-only)
- If no commit yet on this branch (HEAD === origin/main): create commit
- Commit message templated from:
- Linear ticket from state (
SYN-XXX)
- List of changed file paths (top 5)
- Pre-gate results summary (build/test/sanity/smoke all green)
fix(<scope>): <SYN-XXX> — <ticket title>
Files changed:
<top 5 file paths>
Verification:
- npm run type-check → 0 errors
- npm run lint → 0 errors
- npm test → <X> passed / 0 failed
- npm run build → ✓ Compiled successfully
- npm audit --omit=dev → 0 HIGH
Tracked: <SYN-XXX>
Step 2: Push
git push -u origin <branch>
If rejected with non-fast-forward:
git fetch origin
git rebase origin/<branch>
- If rebase has conflicts: STOP, escalate
## P1 — Rebase conflict on push, do NOT push, do NOT force
- If rebase clean: re-attempt push (one retry only)
Step 3: Open PR
gh pr create --repo CleanExpo/Synthex \
--title "<title from commit>" \
--body "$(cat <<'EOF'
## Summary
<bulleted summary from commit message>
## Verification
<gate results from state file>
## Linear
<SYN-XXX>
🤖 Generated by ship-loop-pr
EOF
)"
If gh pr create returns "pull request already exists for branch":
gh pr view --json url,number to capture existing PR
- Update state with the existing URL/number
- Skip create (treat as success)
Step 4: Update state
Atomic update to layers.pr:
{
"state": "opened",
"url": "https://github.com/CleanExpo/Synthex/pull/<num>",
"number": <num>,
"opened_at": "<iso>",
"ci_state": "pending",
"branch": "<branch>"
}
The ci_state will be polled by master loop on subsequent ticks via gh pr checks <num> (master invokes a quick check rather than re-firing this skill — efficient).
Recipe priorities for this loop
- #11 (non-fast-forward) — rebase + retry
- #12 (PR already exists) — capture existing URL, skip create
Hard rules compliance
- No force push — even on rebase conflict, never
git push --force. Conflicts escalate.
- No
git add -A — explicit file paths only, prevents accidental commit of scratchpad outputs
- No push from main — pre-flight refuses
- Commit messages cite Linear ticket — required by CLAUDE.md hard rule
Verification
- Run on a clean branch with one staged commit; expect successful push + PR open
- Run on clean tree (no changes); expect skip with reason
- Run on
main; expect P0 escalation refusal
- Run with sanity layer red in state; expect skip (master shouldn't have invoked, but defense-in-depth)
Out of scope
- PR review request (gh pr edit --add-reviewer) — separate skill if needed
- PR template enforcement — relies on existing
.github/pull_request_template.md if present
- Auto-tagging issues / setting milestones / setting projects on the PR — defer to senior-pm skill or Linear's GitHub integration