Create Pull Request
Generate the PR content, open the pull request, then leave the repo on its default branch.
Run from the target repo's directory (direnv)
gh and git push authenticate with the GITHUB_TOKEN that direnv loads from the .envrc of the current working directory. Opening a PR from a directory whose .envrc belongs to a different repo uses the wrong account's token, and the PR fails (or pushes to the wrong place).
Before Step 1, make the repo whose changes you are PR-ing the working directory — in its own Bash call:
cd /path/to/that-repo # or, when already inside it: cd "$(git rev-parse --show-toplevel)"
Run the cd as a separate call — never chain it as cd … && gh …. direnv reloads .envrc on the next prompt, so the following calls pick up the correct token; a command on the same line as the cd still runs with the old environment.
Step 1: Gather Information
YOU MUST EXECUTE THESE COMMANDS IN ORDER. DO NOT SKIP ANY STEP.
Step 1.1: Capture branch info (used in later steps):
DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "master")
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
echo "Default: $DEFAULT_BRANCH | Current: $CURRENT_BRANCH"
Step 1.2: Get file change summary (THIS IS CRITICAL - you must see ALL files):
echo "=== COMMITTED AHEAD OF BASE ===" && git diff ${DEFAULT_BRANCH}...HEAD --stat -- ':!docs/soup.md' ':!.soup.json' && echo "=== STAGED ===" && git diff --cached --stat -- ':!docs/soup.md' ':!.soup.json' && echo "=== UNSTAGED ===" && git diff --stat -- ':!docs/soup.md' ':!.soup.json'
Step 1.3: Get the full diff (committed + staged + unstaged changes):
echo "=== COMMITTED AHEAD OF BASE ===" && git diff ${DEFAULT_BRANCH}...HEAD -- ':!docs/soup.md' ':!.soup.json' && echo "=== STAGED ===" && git diff --cached -- ':!docs/soup.md' ':!.soup.json' && echo "=== UNSTAGED ===" && git diff -- ':!docs/soup.md' ':!.soup.json'
NOTE: Include ALL three sections (committed, staged, unstaged) in your analysis. Changes may appear in any combination depending on the workflow.
Step 1.4: Find the PR template:
cat .github/pull_request_template.md 2>/dev/null || cat .github/PULL_REQUEST_TEMPLATE.md 2>/dev/null || echo "No PR template found"
Step 1.5: Check for JIRA ticket:
echo $JIRA_TICKET
CRITICAL: The PR summary MUST mention ALL files shown in the Step 1.2 --stat output. Count the files and verify your summary accounts for all of them.
Step 2: Generate PR Content
Generate the commit message, PR title, and PR body following the guidelines below, and show them to the user in this exact format so the inputs to the upcoming gh pr create are visible and reviewable:
COMMIT MESSAGE:
<one line, max 80 characters>
---
PR TITLE:
<one line, max 80 characters>
---
PR BODY:
<filled PR template - can contain any valid markdown>
Formatting rules for this block:
- Section labels must be plain text exactly as shown: "COMMIT MESSAGE:", "PR TITLE:", "PR BODY:"
- Do NOT use markdown formatting on the labels (no bold, no
code blocksaround them) - Separate sections with exactly "---" on its own line
- The PR BODY content can contain any valid markdown (code blocks, lists, etc.)
After printing this block, continue immediately with Step 3 — do not stop here.
Step 3: Commit Any Uncommitted Changes
If there are unstaged or staged-but-uncommitted changes, commit them now using the commit message from Step 2. If the working tree is already clean, skip this step.
git add -A
git diff --cached --quiet || git commit -m "<commit message from Step 2>"
Step 4: Run Tests Locally (gate before pushing)
Run the project's existing test suite before pushing. A red CI run that a local test would have caught is exactly what this step exists to prevent. If a test harness exists and you have not run it, do not push.
Step 4.1 — Detect the runner from the repo, including compiled/mobile stacks:
- JS/TS:
package.jsonscripts →npm test/yarn test/pnpm test - Ruby:
Gemfile+spec/→bundle exec rspec;test/→bin/rails test - Python:
pytest.ini/pyproject.toml/tox.ini→pytest - Go:
go test ./... - .NET:
*.csproj/*.sln→dotnet test - Swift / iOS (you are on macOS — these ARE runnable locally):
Package.swift(SwiftPM) →swift test*.xcworkspace/*.xcodeproj→xcodebuild test -scheme <Scheme> -destination 'platform=iOS Simulator,name=iPhone 15'- List schemes with
xcodebuild -list -workspace <name>.xcworkspace(or-project <name>.xcodeproj) and pick the app/test scheme. - Use
-workspacewhen a.xcworkspaceexists (CocoaPods/SPM workspaces), otherwise-project.
- List schemes with
Step 4.2 — Run the suite covering your changes and paste the runner's own pass marker as proof:
** TEST SUCCEEDED ** / Test Suite '...' passed (Xcode), 0 failures (rspec), passed/N passed (pytest), ok (go). A bare "tests pass" without the runner's output does not count. If anything fails, fix it before pushing — never push red.
Step 4.3 — The "can't run it locally" exception is narrow. On macOS, swift test and xcodebuild test against the iOS Simulator run locally; "slow to build" or "needs a scheme" is not an excuse to skip — find the scheme and run it. Only skip when the suite needs infrastructure genuinely absent on this machine (live external services, physical hardware), and say so explicitly. If the repo has no test harness at all, state that and continue.
Step 5: Push the Branch
git push -u origin "$CURRENT_BRANCH"
Step 6: Open the Pull Request
Prefer mcp__github__create_pull_request when the GitHub MCP server is available. Otherwise use the GitHub CLI:
PR_URL=$(gh pr create --base "$DEFAULT_BRANCH" --head "$CURRENT_BRANCH" --title "<PR title from Step 2>" --body "<PR body from Step 2>")
open "$PR_URL"
If a PR already exists for $CURRENT_BRANCH (e.g., the caller already opened it), gh pr create will fail — treat that as success and continue to Step 7.
Step 7: Monitor CI in the Background
A pushed PR is not done until its checks are green — but CI can take several minutes, so watch it without blocking the session. Launch the watch as a background task, then continue straight to Step 8. The background watch re-invokes you when CI settles, so a failure is still caught and triaged in this session — just not by sitting idle.
Read CI status with the Actions runs REST API — not gh pr checks. This workflow authenticates with a fine-grained PAT, which has no "Checks" permission (the Checks API is GitHub-App-only), so gh pr checks always fails here with Resource not accessible by personal access token on statusCheckRollup...contexts. Do not use it. The REST actions/runs endpoint uses the Actions permission the PAT does have:
SHA=$(git rev-parse "$CURRENT_BRANCH")
# Poll until the run completes, then list each job's conclusion.
while :; do
read -r STATUS RUN_ID < <(gh api "repos/{owner}/{repo}/actions/runs?head_sha=$SHA&event=pull_request&per_page=1" --jq '.workflow_runs[0] | "\(.status) \(.id)"')
[ "$STATUS" = "completed" ] && break
sleep 30
done
gh api "repos/{owner}/{repo}/actions/runs/$RUN_ID/jobs" --jq '.jobs[] | {name, status, conclusion}'
Run the loop with the Bash tool's run_in_background: true (do not foreground it) so the session is not blocked; it re-invokes you when the run completes. Any job conclusion other than success / skipped / neutral is a failure.
Xcode Cloud: its result only surfaces as a check run, which the fine-grained PAT cannot read — so it does not appear in
actions/runs. For Xcode Cloud pass/fail and logs, use theappstoreskill.
When the background watch completes:
- All jobs green: report it; nothing more to do.
- A job failed: Step 8 has likely already returned you to the default branch, so re-checkout the PR branch first (
git checkout "$CURRENT_BRANCH"), then pull the failing logs, fix the cause, and push again (re-run Steps 4–7). Do not leave the PR with a red required check.- GitHub Actions:
gh run view "$RUN_ID" --log-failed. - Xcode Cloud: the check tells you only pass/fail. For the failing test names and logs, use the
appstoreskill —asc-mcpdoes not expose theci*endpoints, so the failing.xcresultmust be fetched via the App Store Connect API and read withxcrun xcresulttool.
- GitHub Actions:
If actions/runs returns no run for the SHA and the repo has no Actions workflows, there is no CI to wait on — note that and continue.
Step 8: Return to Default Branch
Leave the repo on the default branch so the user is back at a clean starting point:
git checkout "$DEFAULT_BRANCH"
git pull --ff-only
Skip this step when running inside a git worktree. A branch can only be checked out by one worktree at a time, so git checkout will fail (or pull the branch out from under the main checkout). Detect with:
git rev-parse --is-inside-work-tree >/dev/null && [ "$(git rev-parse --git-common-dir)" != "$(git rev-parse --git-dir)" ] && echo "in worktree"
In a worktree, leave the branch in place and let the caller cd back to the main checkout.
Commit Message Guidelines
- One line only, maximum 80 characters
- Start with a verb (Add, Fix, Update, Remove, Refactor, etc.)
- Be specific but concise
- No period at the end
- NO footers, NO co-authors, NO signatures
PR Title Guidelines
- One line only, maximum 80 characters
- Should summarize the overall purpose of the PR
- Can be similar to commit message but may be slightly more descriptive
PR Body Guidelines
Summary
IMPORTANT: The Summary section heading must be ## Summary (h2), not # Summary (h1).
Structure the summary as follows:
- Start with a short paragraph describing the big picture of the changes
- Follow with Key changes: (bold)
- Add a bullet list of all changes made, one per line. Similar changes can be summarized together.
Types of changes
CRITICAL: Preserve ALL checkbox items from the template exactly as they appear. Mark applicable items with [x] and leave non-applicable items as [ ]. Never delete, modify, or omit any checkbox items from the original template.
Checklist
CRITICAL: Preserve ALL checkbox items from the template exactly as they appear. Mark applicable items with [x] and leave non-applicable items as [ ]. Never delete, modify, or omit any checkbox items from the original template.
Jira Tickets
If the PR template does NOT contain a Jira Tickets section:
- Do not add one
If the PR template contains a Jira Tickets section:
- If
JIRA_TICKETenv var is set: replace any placeholder (e.g.,XXX-XXXX) with the value from the environment variable - If
JIRA_TICKETenv var is NOT set or empty: omit the entire Jira Tickets section from the output
Further comments (if required)
This section should ONLY be filled if one of the following applies:
- Breaking changes are introduced
- Complex database migration is required
- Reprocessing of existing data is required
If NONE of the above apply, omit this entire section from the output.
If the section is required, write a paragraph explaining the breaking changes, complex database migration, or reprocessing of existing data with any useful information for the reviewer to understand why it is needed and what actions to take.
Note: When this section is filled due to database migration or reprocessing of existing data, the corresponding checklist item about database changes requiring migration/downtime/reprocessing should also be marked with [x].
Important Rules
- Run from the repo whose changes you are PR-ing (see "Run from the target repo's directory" above) —
gh/git pushuse theGITHUB_TOKENdirenv loads for the current directory, so the wrong directory means the wrong token - NEVER add "Generated with Claude Code" or similar signatures to commit messages or PR body
- NO emojis unless explicitly requested
- Before generating PR content, ensure the
run-lintersskill has been executed to verify code quality - Run the existing test suite locally before pushing (Step 4) — for Swift/iOS that means
swift testorxcodebuild test, which run on this Mac; never push a behavior change without running its tests, and never claim tests pass without the runner's own pass marker - After pushing, watch CI in the background (Step 7) — read status with the Actions runs REST API (
actions/runs?head_sha=…→/jobs), notgh pr checks(the fine-grained PAT can't read check runs). Run the poll loop withrun_in_background: trueso the session is not blocked, then triage when it reports; the PR is not done while a required check is red. Xcode Cloud results don't appear inactions/runs— use theappstoreskill for those. - The skill is not done until Step 8 has run (or has been deliberately skipped because of a worktree). Do not stop after printing the Step 2 block.