pr-create
PR Create
Hard rules
These override anything else. No exceptions without an explicit user instruction.
- Never append Claude attribution trailers. No
🤖 Generated with Claude Code, noCo-Authored-By: Claude …, no "Generated by" footers. The PR title and body contain nothing that identifies the author as an AI. - Never force-push unless the user explicitly asks this turn — and only
--force-with-lease, never plain--force. Confirm the head branch is notmain/masterbefore the push. - Never open a PR from
main/master. Stop and surface to the user. - Never auto-rebase or auto-merge the base branch. If the branch is behind, surface it and ask.
- Never fill a checklist item you haven't verified. Leave
[ ]unchecked; don't tick boxes on the user's behalf. - Never open a PR that contains secrets. If the diff touches
.env*,*credentials*,*.pem,*.key, orid_rsa*, stop and surface to the user — even if the user asked you to proceed. Public PRs cache forever.
Workflow
- Preflight — validate branch state, remote state, and
ghauth. - Gather context — commits, diff, branch name, referenced issues, existing PR template.
- Draft title and body — Conventional Commits title + Summary covering why and what.
- Propose for approval — show the draft; wait for edits or OK.
- Push and create — push the branch, open the PR, return the URL.
Step 1 — Preflight
Run these in parallel and stop if any check fails:
gh auth status
git rev-parse --abbrev-ref HEAD
git status --porcelain
BRANCH=$(git rev-parse --abbrev-ref HEAD)
gh pr list --head "$BRANCH" --state open --json url,number
Fail-stop conditions:
| Check | Condition | Action |
|---|---|---|
| Current branch | main or master |
Stop. Tell the user to switch to a feature branch. |
gh auth status |
not authenticated | Stop. Tell the user to run gh auth login. |
| Existing PR | a PR already exists for this branch | Stop. Return the existing URL — don't open a duplicate. |
| Uncommitted changes | git status --porcelain is non-empty |
Surface to the user. This skill is PR-only; don't auto-commit. |
Determine the base branch (usually main, sometimes master or develop):
gh repo view --json defaultBranchRef --jq .defaultBranchRef.name
Check the branch is up to date with the base. Fetch first so the comparison is accurate:
git fetch origin
BASE=$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name)
BEHIND=$(git rev-list --count HEAD..origin/$BASE)
If BEHIND > 0, surface it: "Your branch is N commits behind origin/$BASE. Rebase or merge before opening the PR?" — do not auto-rebase.
Step 2 — Gather context
Collect the material needed to write the title and body:
BASE=$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name)
git log --reverse --pretty=format:'%h %s%n%b%n---' origin/$BASE..HEAD
git diff --stat origin/$BASE...HEAD
git rev-parse --abbrev-ref HEAD
Sensitive-file scan. Check the file list from git diff --stat for paths matching .env*, *credentials*, *.pem, *.key, or id_rsa*. Any match → stop and surface to the user per the hard rule. Do not proceed even if the user tells you to; require a direct re-confirmation that the file is intentional.
Issue references. Scan commit subjects, commit bodies, and the branch name for #NNN patterns. Only promote to Closes #NNN when the signal is unambiguous:
- A commit subject matches
^(fix|close|resolve)(\(.+\))?:and contains#NNNon the same line. - The branch name starts with
fix/NNN-…orissue-NNN-….
Every other reference (#NNN in a body, a refactor commit mentioning an issue, a feat that relates to but doesn't close an issue) is Refs #NNN. Default to Refs when in doubt and flag for the user in Step 4 — a wrong Closes silently closes an issue on merge, a wrong Refs is harmless.
PR template. Check for a template and use it as the body scaffold if it exists. GitHub looks in several locations — check all of them:
ls .github/pull_request_template.md \
.github/PULL_REQUEST_TEMPLATE.md \
pull_request_template.md \
PULL_REQUEST_TEMPLATE.md \
docs/pull_request_template.md \
docs/PULL_REQUEST_TEMPLATE.md \
2>/dev/null
ls -d .github/PULL_REQUEST_TEMPLATE/ 2>/dev/null
If .github/PULL_REQUEST_TEMPLATE/ is a directory (multi-template repo), list its contents and ask the user which template applies before filling.
If a template is found, fill its sections from the gathered context. If it has a checklist, leave items unchecked unless you've verified them.
Step 3 — Draft title and body
Title
Conventional Commits: type(scope): subject
- type —
feat,fix,docs,refactor,test,chore,perf,build,ci. - scope — optional; the primary package/module/area touched. Derive from paths in
git diff --stat. - subject — imperative mood, lowercase, no trailing period, under ~70 chars total.
If the branch has a single commit, reuse its subject when it already follows Conventional Commits. Otherwise, synthesize a new subject that covers the dominant change.
Body — default structure (when no repo template exists)
## Summary
<2–4 sentences. Lead with *why* — the problem, the user need, the constraint that forced this. Then *what* — the concrete change. Avoid restating the diff.>
## Changes
- <bullet per meaningful change, grouped by concern>
- <…>
Closes #NNN
Summary rules:
- Start with the motivation (why), not the implementation (what).
- One paragraph, not a wall of text. If you need headings inside Summary, the change is probably two PRs.
- Never write "This PR …" — write the change directly.
- No hedging ("attempts to", "should hopefully"). State what it does.
Body — when a repo template exists
Fill the template's sections verbatim (don't rename or reorder). Map:
- Template "Summary" / "Description" / "What" → the Summary content above.
- Template "Why" / "Motivation" → the why from the Summary (split it if the template separates them).
- Template "Testing" / "Test Plan" → describe what was tested if verified, otherwise leave blank with a note for the user to fill in. Do not fabricate test steps.
- Template "Checklist" → unchecked
[ ]unless verified. - Template "Issue" / "Related" →
Closes #NNN/Refs #NNN.
Step 4 — Propose for approval
Print the full draft — exactly as it will be submitted — and wait. Format:
Title: feat(tracer): stream spans to disk without blocking the agent loop
Base: main ← <current-branch>
Draft: no
---
## Summary
…
## Changes
- …
Closes #42
---
If there's any ambiguity (close vs refs, unclear scope, behind base), list it under the draft as numbered clarifications. The user edits by number or gives a free-form correction. Re-render the draft after edits and re-confirm before proceeding.
Do not proceed without explicit approval. A silent response is not approval.
Step 5 — Push and create
Push the branch (set upstream if missing). No --force variants.
git push -u origin HEAD
Create the PR with a HEREDOC so the body formats correctly:
gh pr create \
--base "$BASE" \
--title "feat(tracer): stream spans to disk without blocking the agent loop" \
--body "$(cat <<'EOF'
## Summary
Span export was blocking the agent loop under load because the exporter flushed synchronously on every span. This moves writes to a background task with a bounded channel so the hot path stays non-blocking.
## Changes
- Replace synchronous `flush()` with a background writer task
- Add bounded channel with `warn!` on dropped sends
- Cover writer shutdown in `tracer::tests::shutdown_drains_queue`
Closes #42
EOF
)"
Return the PR URL to the user. Done.
Edge cases
Fail-stop conditions are already covered in Step 1 (main/master, uncommitted changes, existing PR, no auth). The cases below are the genuinely ambiguous ones.
| Situation | Handling |
|---|---|
| No upstream set | Use git push -u origin HEAD — don't prompt. |
| Branch behind base | Surface to user; do not auto-rebase or auto-merge. |
| Dirty commit history (WIP, "fix typo") | Flag in Step 4 as a clarification; offer squash as a follow-up the user runs, not as an auto-action. |
| Template present but empty/malformed | Fall back to the default body structure; note it in Step 4. |
Multi-template repo (.github/PULL_REQUEST_TEMPLATE/ directory) |
List available templates; ask the user which one applies. |
Issue reference is ambiguous (#42 in a refactor commit) |
Default to Refs #42, flag for the user. |
User requests --draft |
Add --draft to gh pr create; leave everything else unchanged. |
| User requests force-push | Only then, and only --force-with-lease; confirm the head (current) branch is not main / master. |