Installs into .claude/skills of the current project.
Are you the author of Git Pr?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/erclx-git-pr)
---
name: git-pr
description: Generates pull request titles and descriptions from git diffs. Use for any PR creation or update, such as when asked to "open a pull request".
metadata:
family: ship
---
# Git PR
## Context
Read these files in parallel:
- `${CLAUDE_SKILL_DIR}/../../standards/branch.md`: branch format, valid types, and constraints
- `${CLAUDE_SKILL_DIR}/../../standards/pr.md`: structure, rules, and banned phrases
- `${CLAUDE_SKILL_DIR}/references/labels.md`: label map format, matching, and the missing-label warning. Skip when the project has no `canon/config/pr-labels.toml` and no `.claude/canon/pr-labels.toml`.
- `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: punctuation and formatting for all generated text
- The `write-human` skill: voice, rhythm, and sentence construction for all generated text
- `${CLAUDE_SKILL_DIR}/../../standards/versioning.md`: phase label vs semver discipline
Resolve the base ref first, because the log range and the diff below both consume it:
```bash
git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main 2>/dev/null
```
Then run these commands in parallel to gather git context:
- `git remote get-url origin 2>/dev/null || echo "NO_REMOTE"`
- `git branch --show-current 2>/dev/null || echo "unknown"`
- `git log <base>..HEAD --oneline 2>/dev/null || echo "NO_COMMITS"`
- `git diff <base> HEAD -- . ':(exclude)*.lock' ':(exclude)*-lock.json' 2>/dev/null || echo "NO_DIFF"`
- `git diff --name-only <base> HEAD 2>/dev/null || echo "NO_FILES"`
## Diff baseline
Prefer `origin/main` over local `main`. Both reads resolve against `<base>`, so the commits listed and the changes described come from one scope. Never read a two-dot range against local `main` for either half: it resolves no merge base, so once local `main` moves, the diff reports its newer commits as reversed changes and the log lists commits already on the remote.
The baseline is unusable in two cases:
- No merge base resolves against either ref.
- The base equals HEAD, whichever ref resolved it. Nothing is committed ahead of the base to compare against.
Either case leaves both reads empty, which the no-commits guard below catches. Stop there rather than composing a description from an empty diff.
## Guards
- If branch name does not match `<type>/<description>` format (valid types are defined in `${CLAUDE_SKILL_DIR}/../../standards/branch.md`), stop and output:
`❌ Branch name does not follow conventions. Run /git-branch to rename first.`
- If no commits ahead of main, stop and output:
`❌ No commits ahead of main. Nothing to PR.`
## Response format
### Preview
- **Title:** <title>
- **Files changed:** <count>
- **Analysis:** <brief summary of impact>
After outputting the preview, execute the final command immediately. Claude Code's tool permission dialog is the confirmation gate. Do not wait for user input.
### Testing checkboxes
Follow Testing discipline in `${CLAUDE_SKILL_DIR}/../../standards/pr.md`. Run each check before writing its line, then tick the box and state the result the run reported. Never pre-check based on intent or past sessions.
Leave a box unchecked only for the human-only cases the reference defines, and name which human and why on the same line. A request for the reviewer is not a test result, so it belongs under `## For the reviewer` rather than in the Testing list.
### Pre-publish scan
Before running the final command, run the scan in `${CLAUDE_SKILL_DIR}/../../standards/publish.md` against the PR title and body. The title and body go straight to the remote with nothing checking them on the way, so run the scan regardless of what backs it downstream. It covers the phase-label check as well as the characters, since both go to a reader who has no task board. It applies on top of the banned phrases in `${CLAUDE_SKILL_DIR}/../../standards/pr.md`.
Write the body here, ahead of the final command, since the scan below needs a file to read it from:
```bash
mkdir -p .canon/tmp/pr
cat <<'BODY' > .canon/tmp/pr/body.md
<body content following pr.md template exactly>
BODY
```
Then run:
```bash
canon labels scan --title "<title>" --body-file .canon/tmp/pr/body.md --json
```
Branch on the JSON record rather than the exit code. An operator's shell can wrap `canon` in a function whose status comes from a trailing command, flattening a non-zero exit to 0, the same reason the labels step below branches on its own record rather than the exit.
Stop and fix the title or body on a non-empty `phaseLabels`, `boardReferences`, `sessionLinks`, `unspelledWords`, or `titleFormatIssues`. Do not proceed to `### Final command` until a re-run comes back clean on all five. `titleFormatIssues` names which `## Title` rule in `${CLAUDE_SKILL_DIR}/../../standards/pr.md` the title breaks, structure, casing, or length, so fix the named rule rather than guessing. Leave `cutsRelease` and `semverTags` alone, since a release-please pull request legitimately carries version references its own fixed shape explains.
The scan stays required even where a `pull_request` workflow job runs the same verb, since a project holding an older `canon` carries no such job and no `titleFormatIssues` key.
### Resolving the pull request
The run resolves the pull request once, in the final command below, and every later step reads what that command printed. Nothing else looks the number up again.
Never detect by head branch alone. A lookup that ignores state returns the closed pull request a reused branch name left behind, and the run rewrites it. Scoping the lookup to `state=open` returns empty there and sends the run down the create path.
The lookup scopes to the base as well as the head. One head can carry open pull requests against two bases, and a lookup reading the first result would pick between them by list order. Resolving the base from the repository's default branch is what makes the detection and the create agree on which pull request the run is about.
Every call goes through `gh api` against the REST endpoints rather than through `gh pr` or `gh repo view`, which run on GraphQL. A cloud session's GitHub access blocks GraphQL and serves REST, so the same block converges on a laptop and on a cloud VM.
A detached HEAD gives `git branch --show-current` an empty result, which would read as no open pull request and create a second one. The branch-name guard above stops the run first, since an empty name does not match `<type>/<description>`.
### Labels
Read `${CLAUDE_SKILL_DIR}/references/labels.md` on reaching this step, starting at its `## Labels at run time` section, for the verb to ask, the record to branch on, the fallback, and when `pr_labels` stays empty. The final command below takes the set it resolves as `pr_labels`.
### Final command
Read `${CLAUDE_SKILL_DIR}/references/final-command.md` on reaching this step for the one command that pushes, finds or creates the pull request over REST, re-scans its live body for a session link, applies the labels, and prints `number=`, `url=`, and `head=`, and for the check binding every write to this branch's pull request.
### Post the rendered-surface evidence
Run this step on every pull request this skill opens or edits. Read `${CLAUDE_SKILL_DIR}/references/evidence.md` for finding the checklist and the local server, posting the evidence comparison, posting the checklist alone, and posting the preview address, each against the `<number>` the final command printed. Never judge from the diff whether an evidence image changed, since the verb answers `no-evidence` for a branch that changed none and a session cannot see an image it did not author.
### Record the number on the task
Write the `number` the final command printed onto the task the branch is closing, through `canon tasks pull-request`, and never resolve it again. Read `${CLAUDE_SKILL_DIR}/references/task-number.md` on reaching this step for which task it names, the invocation, the refusals it skips silently, and why the number lands here. Skip it when the final command refused on a head mismatch and printed no number.
## After execution
Respond with one line, using the `url` the final command printed:
`✅ PR: <url>`
Add a line for each `uncovered` path the labels step reported, naming the path and the map it belongs in:
`⚠️ No label covers <path>. Add a row to canon/config/pr-labels.toml or a [declined] entry.`
Add a further line only when the labelling command printed its warning, quoting the label `gh` refused:
`⚠️ Labels not applied: <what gh reported>`
Add a line when a checklist was posted, naming the pull request it landed on and which comment carries it:
`📋 Posted the UI checklist to <number>, in the evidence comment.`
`📋 Posted the UI checklist to <number>, on its own.`
Add a line when the preview step returned an address:
`🔗 Preview: <url>`
Add a line when the evidence step posted the local link:
`🖥️ Local preview: <url>`
Add a line instead when the local step refused as `no-server` or `no-listener-reader` and the evidence or checklist step posted something, since that pull request changes a rendered surface and a reviewer could have used the link. To add it later, start the server, run `canon pr local --json`, then run `canon pr evidence <number> --local <url> --json`, adding `--checklist` when one is still owed, and post the body the way the evidence step does. `git-followup` only carries a line forward, so it cannot add one the comment never had:
`🖥️ No local preview: <reason>`
Do not add any other text.