Claude-skills open-pr

Create a GitHub PR with smart defaults, detailed body generation, and user confirmation

install
source · Clone the upstream repo
git clone https://github.com/mosamaasif/claude-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/mosamaasif/claude-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/open-pr" ~/.claude/skills/mosamaasif-claude-skills-open-pr && rm -rf "$T"
manifest: skills/open-pr/SKILL.md
source content

/open-pr — GitHub Pull Request Creator

Create a GitHub PR with inferred defaults, a detailed generated body, and mandatory user confirmation before creation. Project-agnostic — works with any repo. Supports PR templates, CODEOWNERS, conventional commits, monorepos, and multiple ticket systems out of the box.

Argument Parsing

Parse arguments from the skill invocation string. All are optional,

--flag value
format:

ArgAliasDefaultDescription
--base
-b
Inferred (see Phase 2)Target branch
--assignee
-a
Current
gh
user
PR assignee
--reviewer
-r
Ask userReviewer(s), comma-separated
--label
-l
Inferred from pathsLabel(s), comma-separated
--images
-i
NoneImage paths for screenshots
--draft
-d
Ask userCreate as draft
--no-draft
Create as ready for review
--title
-t
Auto-generatedOverride title
--ticket
-j
Extracted from branchOverride ticket ID

Execution: 5 Phases

You MUST execute all 5 phases in order. Do NOT skip Phase 4 (confirmation).


Phase 1 — Preflight Checks

Run these checks in parallel where possible:

  1. Auth check: Run
    gh auth status
    . If not authenticated, stop and tell the user to run
    gh auth login
    .
  2. Uncommitted changes: Run
    git status --porcelain
    . If output is non-empty, warn the user about uncommitted changes and ask whether to proceed or abort.
  3. Existing PR: Run
    gh pr view --json url,state 2>/dev/null
    . If a PR already exists for this branch:
    • Show the existing PR URL and state
    • Ask user: "A PR already exists. Would you like to update it with
      gh pr edit
      instead?"
    • If yes, switch to edit flow (re-generate body, run
      gh pr edit
      )
    • If no, abort
  4. Commits ahead: Run
    git log {base}..HEAD --oneline
    (after base is determined in Phase 2). If empty, stop — nothing to open a PR for.

Optional Config File

Check for configuration files (strictly optional — skill works perfectly without them):

  1. Check
    .github/open-pr.yml
    (repo-level config)
  2. Check
    ~/.config/open-pr.yml
    (user-level config)
  3. Repo config overrides user config. CLI flags override both.

Supported config keys:

base: develop                    # default base branch
draft: true                      # default draft status
labels:
  path_mappings:                 # custom path→label mappings
    "packages/payments/": "payments"
    "libs/auth/": "auth"
reviewers:
  default: ["alice"]             # default reviewer suggestions
  use_codeowners: true           # enable CODEOWNERS parsing (default: true)
template: ".github/PULL_REQUEST_TEMPLATE/feature.md"  # force a specific template
body_footer: "QA: https://staging.example.com"        # append to PR body

If config files are not found, proceed silently with built-in defaults. Never error on missing config.

CI Status Warning

After confirming the branch has been pushed (or will be pushed), run:

gh run list --branch {branch} --limit 3 --json status,conclusion,name 2>/dev/null

If any recent runs have

conclusion: "failure"
, warn: "Recent CI runs have failures: {names}. This won't block PR creation, but you may want to investigate." This is advisory only — never block on CI status.


Phase 2 — Gather & Infer Parameters

Ticket ID

  • If
    --ticket
    provided, use it directly.
  • Otherwise extract from branch name using priority-ordered patterns:
    1. Jira/Linear:
      ([A-Z][A-Z0-9]+-\d+)
      — e.g.,
      DP-1067
      ,
      ENG-42
    2. Shortcut:
      (sc-\d+)
      (case-insensitive) — e.g.,
      sc-12345
    3. Azure DevOps:
      (AB#\d+)
      — e.g.,
      AB#7890
    4. GitHub issue:
      #(\d+)
      in branch name, or bare number prefix like
      123-fix-bug
      #123
    5. HOTFIX:
      (HOTFIX)
      (case-insensitive)
  • Stop at the first match. If no match, ticket ID is empty — that's fine.

Also scan commit messages for issue references (

#\d+
) to collect related issues for the body.

Base Branch

  • If
    --base
    provided, use it.
  • If config file specifies
    base
    , use that (unless
    --base
    was given).
  • Otherwise detect:
    1. Get the repo default branch:
      gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name'
    2. Check common candidates:
      main
      ,
      master
      ,
      develop
      , plus any
      release/*
      branches
    3. For each candidate that exists, compute
      git merge-base
      distance
    4. Pick the closest ancestor (fewest commits between merge-base and HEAD)
    5. Fallback: repo default branch
  • Verify the base branch exists on the remote. If not, warn and fall back to the repo default branch.

Stacked PR detection: If the resolved base branch is not

main
,
master
,
develop
, or
release/*
, note: "This appears to be a stacked PR targeting
{base}
." Include this note in the Phase 4 preview.

Assignee

  • If
    --assignee
    provided, use it.
  • Otherwise:
    gh api user --jq '.login'

Reviewer

  • If
    --reviewer
    provided, use it.
  • Otherwise use a layered strategy, combining results from all available sources:
  1. CODEOWNERS (top priority): Check for
    .github/CODEOWNERS
    ,
    CODEOWNERS
    , or
    docs/CODEOWNERS
    . If found, parse it and match changed file paths against patterns. Extract usernames/teams. CODEOWNERS format: each line is
    pattern  @owner1 @owner2
    . Match using glob patterns (last matching pattern wins per file, same as GitHub). If config has
    reviewers.use_codeowners: false
    , skip this step.
  2. Recent file authors: For the top 5 most-changed files (by diff stat), run
    git log --format='%aN' -5 -- {file}
    , deduplicate, exclude self.
  3. Recent PR reviewers (fallback):
    gh pr list --limit 15 --state merged --json author,reviews --jq '.[].reviews[].author.login'
    , deduplicate, exclude self.
  • Combine all results. Label each suggestion with its source:
    • "alice (CODEOWNERS for server/)"
    • "bob (recent author of auth.ts)"
    • "carol (recent PR reviewer)"
  • If config specifies
    reviewers.default
    , include those with label "(default from config)".
  • Deduplicate across sources (keep the highest-priority label).
  • Present the combined list to the user via AskUserQuestion (up to 6 options + "Other" + "Skip"). Let them pick one or more.

Labels

  • If

    --label
    provided, use those directly.

  • Otherwise:

    1. Fetch available labels:
      gh label list --json name --jq '.[].name'
    2. If no labels exist in the repo, skip label inference entirely.
    3. Get changed file paths:
      git diff --name-only {base}..HEAD
    4. Infer labels using a dynamic matching strategy:

    Config-based mappings (if config has

    labels.path_mappings
    ):

    • Match changed paths against configured path prefixes. Use the mapped label name if it exists in the repo.

    Dynamic directory-to-label matching:

    • Extract directory names from changed file paths (e.g.,
      packages/payments/src/foo.ts
      packages
      ,
      payments
      ,
      src
      )
    • For each repo label, check if its name (case-insensitive) is a substring of any directory name in the changed paths, or vice versa
    • E.g., label
      payments
      matches paths under
      packages/payments/
      ; label
      auth
      matches paths under
      libs/auth/

    Universal fallback heuristics (always applied):

    • *.md
      files or paths under
      docs/
      → labels containing "doc"
    • Paths under
      test/
      ,
      tests/
      ,
      __tests__/
      , or files matching
      *test*
      ,
      *spec*
      → labels containing "test"
    • Paths under
      .github/
      ,
      .circleci/
      ,
      ci/
      ,
      .gitlab-ci*
      ,
      Jenkinsfile
      ,
      Dockerfile
      ,
      docker-compose*
      ,
      terraform/
      ,
      infra/
      → labels containing "ci", "infra", or "devops"

    Size labels: Calculate total lines changed from

    git diff --stat
    . If the repo has labels matching
    size/XS
    ,
    size/S
    ,
    size/M
    ,
    size/L
    ,
    size/XL
    (case-insensitive):

    • XS: <10 lines, S: <50, M: <200, L: <500, XL: 500+
    • Auto-suggest the matching size label.

    Monorepo package matching: If monorepo detected (see Phase 3), match package/workspace names against available labels.

    1. Present inferred labels to user for confirmation. Let them add/remove before proceeding.
    2. If no labels match, proceed without labels.

Draft Status

  • If
    --draft
    provided → draft = true
  • If
    --no-draft
    provided → draft = false
  • If config specifies
    draft
    , use that as default.
  • Otherwise: ask the user via AskUserQuestion: "Create as draft?" with options "Yes (draft)" and "No (ready for review)"

Phase 3 — Analyze Changes & Generate PR Content

Collect Change Data (run in parallel)

  • git log {base}..HEAD --pretty=format:'%h %s'
    — commit list
  • git diff --stat {base}..HEAD
    — file change stats
  • git diff {base}..HEAD
    — full diff (only if <5000 lines; check with
    git diff {base}..HEAD | wc -l
    first)
  • If diff is >5000 lines, warn the user and rely on commit messages + stats only.
  • Count changed files: if >50, warn about large PR.

Size Classification

Calculate total lines changed (additions + deletions from

git diff --stat
). Classify:

  • XS: <10 lines
  • S: <50 lines
  • M: <200 lines
  • L: <500 lines
  • XL: 500+ lines

Include this in the Phase 4 preview. If XL, add advisory: "This is a very large PR. Consider splitting into smaller, focused PRs for easier review."

Detect Monorepo Structure

Check for monorepo indicators (in parallel with diff collection):

  • pnpm-workspace.yaml
    ,
    lerna.json
    ,
    nx.json
    ,
    rush.json
    ,
    turbo.json
  • Presence of
    packages/
    or
    apps/
    directories at repo root

If detected, identify which packages/workspaces have changes by mapping changed file paths to package directories. This information is used for:

  • Grouping changes in the PR body by package
  • Package-based label matching (Phase 2)
  • Noting in the summary if changes are scoped to a single package

Detect Conventional Commits

Parse the commit list. Check if >50% of commits follow the pattern

type(scope): description
or
type: description
(where type is one of:
feat
,
fix
,
docs
,
style
,
refactor
,
perf
,
test
,
build
,
ci
,
chore
,
revert
).

If conventional commits are detected:

  • Use the predominant type to inform the title prefix (e.g.,
    feat:
    → feature PR,
    fix:
    → bugfix PR)
  • Group changes by type in the body (Features, Bug Fixes, Refactoring, etc.)
  • If any commit contains
    BREAKING CHANGE:
    in its body/footer or uses
    !
    after the type (e.g.,
    feat!:
    ), add a "Breaking Changes" section to the body
  • Still include the ticket ID prefix if present:
    [TICKET-ID] feat: description

If conventional commits are NOT detected (<=50% match), fall back to the current diff analysis behavior.

Detect PR Template

Before generating the body, check for repository PR templates in this order:

  1. .github/PULL_REQUEST_TEMPLATE.md
  2. .github/pull_request_template.md
  3. PULL_REQUEST_TEMPLATE.md
    (repo root)
  4. pull_request_template.md
    (repo root)
  5. Multiple templates in
    .github/PULL_REQUEST_TEMPLATE/*.md
    — if found, list them and ask the user to pick one via AskUserQuestion

If config specifies

template
, use that path directly instead of searching.

If a template is found:

  • Read the template file
  • Preserve its structure: headings, checkboxes, sections, formatting
  • Remove HTML comments (
    <!-- ... -->
    ) and replace them with actual content generated from the diff/commits
  • Fill in each section intelligently:
    • Summary/description sections → summarize from diff analysis
    • Changes/what sections → list actual changes
    • Testing sections → infer test steps from changes
    • Checklist items → check off items that are clearly satisfied by the diff (e.g., "Tests added" if test files are in the diff)
    • Leave unknown/inapplicable sections with a brief note or empty
  • Append the
    *Generated with Claude Code*
    footer

If no template is found, use the built-in template below.

Generate Title

  • If
    --title
    provided, use it.
  • Otherwise:
    • Analyze the commits and diff to determine the primary change.
    • If conventional commits detected: use the predominant type as part of the title format.
    • Format:
      [TICKET-ID] Imperative description
      if ticket exists, or just
      Imperative description
      .
    • For HOTFIX branches:
      [HOTFIX] Imperative description
      .
    • Keep under 70 characters.

Generate PR Body (built-in template — used only when no repo template is found)

## Summary
{2-5 bullets: what changed and why, written from analyzing the actual diff and commits}

## Changes
{If monorepo: group by package with package name as sub-heading}
{If conventional commits: group by type — Features, Bug Fixes, Refactoring, etc.}
{Otherwise: group by area}
### {Area/Package/Type 1}
- {specific change}
### {Area/Package/Type 2}
- {specific change}
(use a flat bullet list instead of grouped headings if changes are simple/few)

{If breaking changes detected:}
## Breaking Changes
- {description of breaking change and migration path}

## Related Issues
{List any GitHub issue references found in commits or branch name}
{Use "Closes #123" for issues this PR fixes}
{Use "Relates to #123" for referenced but not fixed issues}
(OMIT this section if no issue references found)

## Screenshots
{embedded images if URLs were provided}
{TODO: drag-and-drop images in browser — if local file paths were provided}
(OMIT this entire section if no images)

## Testing
- [ ] {test item inferred from the changes}
- [ ] {build/compile verification relevant to the project}
- [ ] {manual verification step if applicable}

---
*Generated with [Claude Code](https://claude.com/claude-code)*

If config specifies

body_footer
, append it before the "Generated with Claude Code" line.

Fill in each section by actually reading and understanding the diff/commits — do not use generic placeholders. The summary and changes sections should reflect what actually changed.


Phase 4 — Confirm with User (MANDATORY — never skip)

Present a full preview to the user. Format it clearly:

=== PR Preview ===

Title:    [DP-1067] Add safe area support
Base:     main
Assignee: mosamaasif
Reviewer: alice (CODEOWNERS), bob (recent author)
Labels:   client, mobile, size/M
Draft:    Yes
Size:     M (142 lines changed)
Template: .github/PULL_REQUEST_TEMPLATE.md
{If stacked PR: "Note: Stacked PR targeting `feature/base-branch`"}
{If monorepo: "Packages: @app/payments, @app/auth"}

--- Body ---
(full PR body from Phase 3)
--- End Body ---

Then ask via AskUserQuestion:

  • "Create this PR?" with options:
    • "Yes, create it"
    • "Edit details" (loop back — ask what to change)
    • "Abort"

If user picks "Edit details", ask what they want to change, apply the change, and show the preview again. Loop until they approve or abort.


Phase 5 — Create PR

  1. Push branch if not already pushed:

    • Check:
      git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null
    • If no upstream:
      git push -u origin HEAD
    • If upstream exists but local is ahead:
      git push
  2. Create the PR using

    gh pr create
    :

    gh pr create \
      --base "{base}" \
      --title "{title}" \
      --assignee "{assignee}" \
      --reviewer "{reviewer1},{reviewer2}" \
      --label "{label1},{label2}" \
      {--draft if draft} \
      --body "$(cat <<'PRBODYEOF'
    {generated body}
    PRBODYEOF
    )"
    
    • Omit
      --reviewer
      if none selected
    • Omit
      --label
      if none selected
    • Omit
      --draft
      if not draft
  3. Display the result:

    • Show the PR URL returned by
      gh pr create
    • If local image files were provided via
      --images
      , remind: "Local images can't be uploaded via CLI. Open the PR in your browser and drag-and-drop the images into the Screenshots section."
    • Post-creation hint: "Monitor CI checks with
      gh pr checks
      ."

Edge Case Handling

  • No ticket ID in branch: Omit
    [TASK-ID]
    prefix from title and omit ticket-related content from body.
  • HOTFIX branches: Use
    [HOTFIX]
    prefix. No ticket link.
  • PR already exists: Detected in Phase 1. Offer
    gh pr edit
    flow.
  • Very large diffs (>5000 lines or 50+ files): Warn user. Rely on commit messages and
    --stat
    for body generation.
  • Local image files: Remind user to manually upload after PR creation.
  • Base branch missing on remote: Fall back to repo default branch with a warning.
  • No labels in repo: Skip label inference silently.
  • No reviewers found: Skip reviewer suggestion, allow manual entry.
  • gh CLI not installed: Stop immediately with install instructions.
  • Not a git repo: Stop immediately.
  • No PR template found: Use built-in template — never error on missing template.
  • No CODEOWNERS file: Skip CODEOWNERS step, fall through to other reviewer strategies.
  • No conventional commits: Fall back to diff-based analysis for title and body grouping.
  • Not a monorepo: Use flat file grouping — no package-level grouping.
  • No config file: Proceed with built-in defaults — config is strictly optional.
  • Config file has unknown keys: Ignore unknown keys silently.
  • Multiple PR templates directory: Ask user to pick one.
  • Stacked PR (non-standard base): Show advisory note, don't change behavior.
  • CI failures on branch: Show warning, don't block PR creation.