Create Pull Request Workflow
STEP 1: Gather Context
Run in parallel (quick reads, not background):
git status- working tree stategit branch --show-current- local branchgit --no-pager log --format=%s --reverse $(git merge-base HEAD main)..HEAD- commits on the branchgit --no-pager diff main- full diffgit rev-parse @{u} 2>/dev/null- upstream presence (already pushed?)gh pr view --json number,url,state 2>/dev/null(only if upstream exists) - check if the branch already has an open pull request
STEP 2: Kick Off Background Work
Everything in this step starts in the background (run_in_background: true) so the workflow proceeds without waiting. Results are reconciled in STEP 10.
Validation
Skip anything already green this session. Only run what's genuinely uncertain.
When validation IS needed:
Run build first:
- Backend change:
build(no flags - covers backend AND frontend; backend changes can break the frontend build via API contracts) - Frontend-only:
build --frontend - CLI-only:
build --cli
After build, in parallel (with --no-build where applicable):
aspire-restart- service / Aspire wiring changedformat --<target>,lint --<target>- for each target whose code changedtest --no-build- backend changee2e- frontend or shared E2E-covered code changed; use--smokefor low-impact changes (judgment)
Use judgment to decide which target a file belongs to (e.g. project / manifest / lock files affect their owning build, even when the extension isn't obvious). Each item runs only if needed and not already green.
STEP 3: Verify Branch Name
Three checks - all read-only at this stage; renames happen in STEP 8.
- Strip
worktree-prefix. Local-only convention; never push it. - Naming convention. Lowercase kebab-case.
- Drift check. If the slug no longer describes the work (e.g.
fix-login-bugwhose commits do a refactor), draft a new name.
If either check 1 or check 3 fires, stash the proposed new name; STEP 4 will ask the user to confirm.
STEP 4: Ask the User
Ask everything upfront so the user doesn't wait through slow research/drafting just to be prompted at the end. Single AskUserQuestion call, up to four questions:
- Labels (multi-select, can pick zero or more):
Enhancement,Bug,Deploy to Staging. - Pull request checklist (single-select): the recommended option spells out every
[ ]item from.github/PULL_REQUEST_TEMPLATE.mdin present-tense (e.g. for the current template: "Confirm you have added tests, done manual regression tests, and updated the documentation"). The alternative is "Leave checklist items unchecked". - Review before pushing (single-select): "Show me the rendered title and description before pushing" / "Push immediately".
- Branch rename confirmation (single-select), only if STEP 3 flagged a rename: "Rename
<current>to<proposed>?" with options Yes / No / Use a different name (free-text).
Stash the answers; consumed in STEP 6, 7, 8, 11.
STEP 5: Correlate to [Task]
Look up .claude/reference/product-management/[PRODUCT_MANAGEMENT_TOOL].md for [Task] ID format and MCP tools.
Parse [Task] ID from branch name. The exact pattern depends on the configured [PRODUCT_MANAGEMENT_TOOL] — check
.claude/reference/product-management/[PRODUCT_MANAGEMENT_TOOL].md. Stripworktree-first if present.If [Task] detected, fetch via MCP. Use its title, description, and parent [Feature] to validate the draft in STEP 6 and spot drift.
Detect unrelated commits (rare). If some commits clearly belong to a different scope, offer to create a new [Task] as future follow-up - written as a fresh problem statement (future tense, what needs to happen). [Feature] choice: parent [Feature] of current branch's [Task] -> [Feature] with [Active] status -> ask.
If no [Task] in the branch name, ask: "No [Task] detected. Create one for this pull request?". If yes, draft a [Task] from the diff (problem statement, not what was done) and create it via MCP. Suggest a [Feature] using the same rules as above.
STEP 6: Draft Title and Description
Title
Write a fresh title based on the actual diff, commits, and [Task] context - not the branch name (branch names are often abbreviated and won't make a good title).
- Imperative form ("Fix", "Add", "Upgrade", "Refactor")
- Sentence case (not Title Case)
- No trailing period
- No "Pull Request" / "PR" / abbreviations
- No personal pronouns ("we", "our", "he", "she")
- No [Task] IDs in the title
Description
Use the official template at .github/PULL_REQUEST_TEMPLATE.md:
- Summary & Motivation: lead with the most important change, explain what and why (not how), use bullets for multiple items, mention minor fixes last. Do not end with a closing benefit summary - that is the most common rejection reason.
- No "pull request" or "pull-request" in the body - CI rejects either phrase. Use "this change", "the change", etc.
- Checklist: copy items from the template. Render every
[ ]as[x]if the user picked "Mark all checklist items..." in STEP 4; otherwise leave them as[ ].
Save the rendered title (as a level-1 heading) + body to .workspace/<branch-name>/pull-request.md. Use the un-prefixed branch name if STEP 3 stripped worktree-.
STEP 7: Optional Review Gate
If the user picked "Show me before pushing" in STEP 4:
- Print the rendered title and the body inline.
- Ask "Open pull request with this content? (yes / edit)".
- On "edit", let the user dictate changes. Re-save the markdown. Re-confirm.
Otherwise skip this step.
STEP 8: Branch Hygiene
If STEP 4 confirmed a rename, rename the branch and verify. Safe in a worktree (directory path is independent of branch name). The push step sets the new upstream.
STEP 9: Push
- Re-check upstream tracking with
git rev-parse @{u} 2>/dev/null. - If unset (branch never pushed, or just renamed): ask "Branch is local-only. Push to origin?". On confirm, push and ensure the pushed branch has upstream tracking (
git push --set-upstream origin "$(git branch --show-current)"). - If upstream exists, check divergence:
git log --oneline @{u}..HEAD(local-ahead)git log --oneline HEAD..@{u}(remote-ahead)- If remote-ahead: surface the divergence and stop. Don't force-push without an explicit user request.
- If only local-ahead:
git push.
STEP 10: Wait for Background Validation
Check the status of the background tasks started in STEP 2.
- Done and passed: continue silently.
- Done and failed: print the failure and pause. Do not push or open the pull request until the user decides (fix / push anyway / abort).
- Still running: surface the status. Ask "Wait for completion, or proceed?". If proceeding, mention that CI will re-run these.
- Killed earlier (user opt-out): skip.
STEP 11: Create or Update the Pull Request via gh
Pre-flight: gh auth status. If not authenticated, surface and stop.
Use the existing pull request check result from STEP 1.
If an open pull request already exists for this branch:
gh pr edit <number> \
--title "<title>" \
--body-file ".workspace/<branch>/pull-request.md" \
--add-label "<each-selected-label>"
(Strip the level-1 heading from the body file before passing.) Tell the user the pull request was updated, not newly created. Don't change draft/ready state without an explicit ask.
If no pull request exists:
gh pr create \
--title "<title>" \
--body-file ".workspace/<branch>/pull-request.md" \
--base main \
--assignee @me
Append --label "<name>" per label from STEP 4. @me resolves to the authenticated user. Don't pass --draft; this workflow opens ready-for-review.
Capture the pull request URL from gh's output for STEP 12.
STEP 12: Report
Print:
- The pull request URL (clickable)
- Title
- Labels applied
- [Task] linked via branch name (auto by [PRODUCT_MANAGEMENT_TOOL])
- Pull-request markdown saved at the absolute path
End with a clickable link to the saved file.
Examples
Example 1 - Title
# DO: imperative, sentence case, no period
Add user profile image upload functionality
Fix data protection key sharing between self-contained systems
Upgrade dependency versions to latest stable releases
# DON'T: past tense, period, title case, prefixes
Added User Profile Image Upload Functionality.
PR: Implement new feature
Updating dependencies
Example 2 - Description
### Summary & Motivation
Add data protection key sharing between self-contained systems to fix antiforgery token validation failures. Previously, each self-contained system had isolated encryption keys, causing tokens generated in one system to be invalid in another.
- Configure a common application name for all self-contained systems
- Store keys in a user-accessible directory on disk
# DO: stop here - no closing benefit summary
### Checklist
- [x] I have added tests, or done manual regression tests
- [ ] I have updated the documentation, if necessary
### Summary
# DON'T: personal pronouns, past tense, "pull request" term, vague descriptions
In this pull request we fixed a bug causing issues in our scs's.
- We added some configuration.
- Fixed a bug.
These changes make the system more robust and maintainable. # DON'T: closing summary
Example 3 - Branch with worktree- prefix
Local: worktree-fix-login-redirect -> propose fix-login-redirect (rename in STEP 8, push in STEP 9).
Example 4 - Branch drift
Local: fix-login-redirect. Actual work: refactored the login command and added auto-redirect.
Propose refactor-login-command-with-auto-redirect. Confirm in STEP 4; rename in STEP 8.
Example 5 - Unrelated commits
Branch: fix-login-redirect. Commits: the redirect fix + an unrelated typo fix in README.
Surface the typo commit. Offer to create a new [Task]: "Fix typo in README troubleshooting section" (problem statement, future tense). The pull request continues with both commits.
Example 6 - End-of-output report
Pull request opened: https://github.com/<owner>/<repo>/pull/123
Title: Fix login redirect after OTP verification
Labels: Bug, Deploy to Staging
[Task]: linked via branch name (auto-detected by [PRODUCT_MANAGEMENT_TOOL])
Description: /absolute/path/to/repo/.workspace/fix-login-redirect/pull-request.md