create-pr
Use when asked to create a pull request, open a PR, or submit changes for review. Handles branch verification, change analysis, title and description generation, and gh pr create. Do NOT use for commi
SKILL.md.agents/skills/create-pr/SKILL.md
GitHubCreating a Pull Request
Workflow
Step 1: Verify branch state
CURRENT=$(git branch --show-current)
DEFAULT=$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name')
- If
$CURRENTequals$DEFAULTor isdevelop/release/*/hotfix/*: inform user they are on a protected branch, cannot create PR from here - If uncommitted changes exist: commit first (use git-commit skill)
- If branch not pushed:
git push -u origin $CURRENT
Step 2: Analyze changes
DEFAULT=$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name')
git log --format="%s%n%b" "$DEFAULT..HEAD"
git diff --name-only "$DEFAULT..HEAD"
git diff --stat "$DEFAULT..HEAD"
From the diff and commits, identify:
- Type: primary change type (feat, fix, refactor, chore, perf)
- Intent: business/technical goal (1-2 sentences)
- Entry point: most critical changed file for reviewer
- Sensitive areas: files needing extra scrutiny (auth, payments, data)
- Breaking changes:
!in commits or BREAKING CHANGE footer - Migrations: database or schema changes
Step 3: Resolve the linked issue
Two separate questions decide what the PR takes from an issue:
| Question | When yes |
|---|---|
| Belongs: does every change in the PR serve this one issue, and nothing else? | Copy the issue's labels, milestone and projects to the PR |
| Completes: once merged, is nothing left to do on the issue? | Link it under Development with a closing keyword |
A PR can belong to an issue without completing it: one step of a larger task. A PR that serves several issues, or mixes in unrelated changes, belongs to none of them and gets neither.
Take the first candidate found:
- An issue named in the arguments:
#123,owner/repo#123, or its URL. - A closing footer in the branch's commits:
Closes #123,Fixes #123,Resolves #123. - An issue number in the branch name, as in
fix/123-null-user.
gh issue view <number> --json number,title,state,body,labels,milestone,projectItems
Answer both questions from the issue body and the change from Step 2. Ask the user when there are several candidates, the issue is closed, or either answer is unclear. A wrong "completes" closes the issue on merge. A non-GitHub key such as PROJ-456 carries no metadata; write it under Related Issues as plain text.
When the PR belongs to the issue:
Copy labels and the milestone only from an issue in the same repository. Both are repository-scoped.
Trust an empty
projectItemsonly after checking the token: it comes back empty, without an error, when the token lacks theprojectscope.gh auth status --active --json hosts --jq '.hosts[][].scopes'Without
project, skip projects and tell the user to rungh auth refresh -s project.
Under Related Issues, as plain text:
- Completes: Closes #123, or Closes owner/repo#123 for another repository. The keyword is the only way to fill Development from the CLI, since no API links a PR manually, and GitHub honours it only when the base is the default branch.
- Belongs but does not complete: Part of #123. No closing keyword, so merging leaves the issue open and Development stays empty.
- Otherwise: #123.
Never wrap the line, the keyword or the reference in backticks. GitHub does not parse a closing keyword inside a code span: the PR links nothing and merging closes nothing, with no error.
Step 4: Generate title
Conventional Commits format: <type>[scope]: <description>
- Imperative mood, under 72 chars, no period, English only
- Match the project's commit style (check
git log --format="%s" -20)
NEVER add task ID, issue number, or other metadata to the title:
❌ Wrong:
feat(messages): add server-only synthetic mailbox archive parser (BP-1234)
✅ Correct:
feat(messages): add server-only synthetic mailbox archive parser
Step 5: Generate description
Open assets/pull_request_template.md and read it before writing a line of the body. That file is the only definition of the shape: reproduce every heading it carries verbatim, at the level and with the emoji it gives them, and fill each one. Add no section the template does not define.
Formatting rules:
- No fluff intros ("This PR updates...")
- Filenames in backticks: `path/to/file.ts`
- Use " - " (hyphen), not "-" (em-dash)
- No hard-wrap in body prose: GitHub renders soft line breaks as
<br>in PR descriptions, so wrapping at ~80 chars creates visible artificial breaks. Let paragraphs flow; break only for new paragraphs, list items, or code blocks - All sections required, sub-sections only when relevant data exists
Do NOT reference specifications (./specs/*.md), plans (./plans/*.md), its section numbers, or TODO.md in pull request descriptions. These are internal artifacts for agent coordination and should not be exposed to human reviewers. If you need to explain a design decision, implementation detail, or rationale, do so in the description without citing internal documents. The description should be self-contained and understandable on its own.
Verify every claim about repository state before writing it. A diff supports claims about the change; it supports nothing about the environment the change lands in. The diff shows a workflow referencing CICDBOT_TOKEN - it says nothing about whether that secret exists. Writing "requires CICDBOT_TOKEN to be configured" silently promotes a reference into a missing prerequisite, and the reviewer goes chasing a risk that is not there.
Confirm with a command, or do not assert. When a check is unavailable, address the reviewer instead: "Confirm CICDBOT_TOKEN is set for this repo" is useful; "requires CICDBOT_TOKEN" is a guess wearing a warning's clothes.
| Claim | Check before writing it |
|---|---|
| A secret or variable exists | gh secret list, gh variable list |
| A branch rule or required status check is in force | gh api repos/{owner}/{repo}/rulesets |
| A person, bot, or team has access | gh api repos/{owner}/{repo}/collaborators |
| A repository setting or feature is enabled | gh api repos/{owner}/{repo} --jq '.<field>' |
| An external action, workflow, or tag is reachable | gh api repos/{owner}/{repo} |
| A CI job or workflow with a given name exists | Read .github/workflows/*.yml: jobs.<id>.name is the displayed job name. A step name is not a job name |
| A workflow runs on a given branch or event | Read the workflow's on: block. pull_request-only means the default branch is never checked |
This governs every section, not only Risk Assessment. An unverified line under "Sensitive Areas" costs the reviewer the same time.
Complexity guide:
| Level | Criteria |
|---|---|
| Low | Single file, config, docs, simple fix |
| Medium | Multiple related files, new feature with tests |
| High | Cross-cutting, migrations, breaking changes, security |
Step 6: Create PR
DEFAULT=$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name')
gh pr create \
--title '<title>' \
--body '<description>' \
--base "$DEFAULT"
For drafts, add --draft.
When the PR belongs to an issue (Step 3), add one --label '<name>' per label and --milestone '<title>', omitting any that are empty. Add projects after creation, so that a project failure cannot block the PR:
gh pr edit <number> --add-project '<project title>'
When updating an existing PR, apply the same metadata with gh pr edit --add-label, --milestone and --add-project.
MANDATORY: Use single quotes for --body to avoid shell interpolation. NEVER use double quotes, which can cause variables or special characters in the description to be misinterpreted by the shell.
Step 7: Verify
gh pr view --web
gh pr view --json labels,milestone,projectItems,closingIssuesReferences
When the PR completes an issue, closingIssuesReferences must list it. An empty list means the Related Issues line is malformed, most often a keyword in backticks: fix the body with gh pr edit and check again. It is not propagation delay.
Report: PR number, URL, title, base/head branches, and the issue metadata that was set. Name every field that could not be set, and why.
Error Recovery
| Error | Fix |
|---|---|
| "pull request already exists" | gh pr view to see existing |
| "no commits between" | Verify branch has commits ahead of base |
| Auth failure | gh auth login --web |