This skill retrieves all merged pull requests associated with a GitHub milestone and saves each PR's metadata as a separate JSON file for downstream processing (e.g., release notes generation, changelog creation).
When to Use This Skill
- User asks to fetch or retrieve PRs for a milestone
- User wants to gather PR data before generating release notes
- User needs milestone PR metadata for analysis or categorization
- User mentions a milestone name and wants to see what PRs were included
- As a prerequisite step before invoking the
release-notesprompt
Prerequisites
- The
ghCLI must be installed and authenticated (gh auth login) - Python 3.8+ must be available
Instructions
Identify the milestone name from the user's request. This is typically a version string like
7.0.0-preview4,6.1.5, etc.Identify the repository (optional). Defaults to
dotnet/SqlClient. If the user specifies a different repo, pass it via--repo.Run the fetch script located in this skill's directory:
python .github/skills/fetch-milestone-prs/fetch-milestone-prs.py <milestone> [--repo OWNER/REPO] [--output-dir DIR]Examples:
# Default: outputs to .milestone-prs/<milestone>/ python .github/skills/fetch-milestone-prs/fetch-milestone-prs.py 7.0.0-preview4 # Custom output directory python .github/skills/fetch-milestone-prs/fetch-milestone-prs.py 7.0.0-preview4 --output-dir ./my-prs # Different repo python .github/skills/fetch-milestone-prs/fetch-milestone-prs.py 7.0.0-preview4 --repo dotnet/efcoreVerify the output by checking the generated
_index.jsonfile in the output directory. It contains a summary of all fetched PRs.Report results to the user:
- Total merged PRs found
- Total skipped (closed but not merged)
- Output directory path
- Mention that each PR is in a separate
<number>.jsonfile and the index is at_index.json
Output Format
Directory structure
.milestone-prs/<milestone>/
├── _index.json # Summary index of all PRs
├── 1234.json # Individual PR metadata
├── 1235.json
└── ...
Individual PR file (<number>.json)
Each file contains:
{
"number": 1234,
"title": "PR title",
"author": "github-username",
"author_association": "MEMBER",
"labels": ["label1", "label2"],
"assignees": ["user1"],
"state": "closed",
"merged": true,
"merged_at": "2026-01-15T12:00:00Z",
"merge_commit_sha": "abc123...",
"created_at": "2026-01-10T10:00:00Z",
"closed_at": "2026-01-15T12:00:00Z",
"html_url": "https://github.com/dotnet/SqlClient/pull/1234",
"body": "Full PR description markdown...",
"comments_count": 5,
"is_merged_pr": true,
"has_public_api_label": false,
"has_engineering_label": false,
"has_test_label": true
}
Index file (_index.json)
{
"milestone": "7.0.0-preview4",
"repo": "dotnet/SqlClient",
"total_closed": 76,
"total_merged": 74,
"total_skipped": 2,
"prs": [
{
"number": 1234,
"title": "PR title",
"author": "github-username",
"labels": ["label1"],
"merged_at": "2026-01-15T12:00:00Z",
"has_public_api_label": false,
"has_engineering_label": false,
"has_test_label": true
}
]
}
Derived Fields
The script computes these boolean flags for easy categorization:
| Field | Logic |
|---|---|
has_public_api_label |
Any label contains "Public API :new:" |
has_engineering_label |
Any label contains "Engineering" |
has_test_label |
Any label contains "Test" |
Error Handling
- If the milestone is not found, the script prints available milestones and exits. Ask the user to verify the milestone name.
- If
ghis not authenticated, the script will fail with an auth error. Instruct the user to rungh auth login. - If a specific PR detail fetch fails, the script exits. This is rare but can happen with API rate limits — suggest waiting and retrying.
- On Windows, if encoding errors occur, the script uses
errors="replace"to handle non-UTF-8 characters gracefully.
Using Output with Other Workflows
The output files are designed to be consumed by:
- The
release-notesprompt — tag the output directory (e.g.,@.milestone-prs/7.0.0-preview4/*) when invoking it - Manual analysis — read
_index.jsonfor a quick overview, individual files for PR details - Any script or agent that needs structured PR metadata