azure/generate-release-notes
> Auto-generate release notes from a list of PRs for Azure Functions Python Worker components (worker, runtime v1, or runtime v2). Analyzes PR file changes to determine component relevance, groups by category/prefix, and outputs organized release notes. Use when the user asks to "generate release notes", "create release notes from PRs", "format these PRs for release", or provides a list of PRs to organize.
npx skills add https://github.com/Azure/azure-functions-python-worker --skill generate-release-notes
Auto-generate organized release notes from a list of PRs for Azure Functions Python Worker components.
Accept:
fix:, feat:, build:, etc.)#number)worker - Python Worker componentruntime v1 - Runtime V1 componentruntime v2 - Runtime V2 componentExample input format:
## What's Changed
* fix: fix protobuf import for V2 by @hallvictoria in https://github.com/Azure/azure-functions-python-worker/pull/1736
* feat: allow event loop to be uvloop by @EvanR-Dev in https://github.com/Azure/azure-functions-python-worker/pull/1697
...
For each PR in the list, extract:
#number)Store this information for later use.
For each PR, determine which files were changed:
Using GitHub CLI:
gh pr view <number> --json files | ConvertFrom-Json
This returns the list of changed files in the PR.
For each PR, analyze the changed files and classify based on paths:
| Path Pattern | Component | Rule |
|-------------|-----------|------|
| runtimes/v1/** | runtime v1 | Any file inside runtimes/v1/ directory |
| runtimes/v2/** | runtime v2 | Any file inside runtimes/v2/ directory |
| workers/** | worker | Any file inside workers/ directory |
| Other paths | all | Changes outside these directories affect all components |
Component matching logic:
runtimes/v1/ files changed → runtime v1 onlyruntimes/v2/ files changed → runtime v2 onlyworkers/ files changed → worker onlyWorker-specific exclusions:
Some PRs should ONLY appear in worker releases, even if they change root-level files:
azure-functions package version in workers/pyproject.tomlworkers/pyproject.toml (dependency update)workers/*/version.pyworkers/azure_functions_worker/version.py or similarDetection logic for worker-specific PRs:
If PR title matches "Update Python SDK Version" OR "Update version to":
Check if files changed include:
- workers/pyproject.toml (for SDK updates)
- workers/*/version.py (for worker version updates)
If yes:
- Include in worker release ONLY
- Exclude from runtime v1 and runtime v2 releases
Filter PRs based on the user-specified component:
Parse the PR title prefix to determine the category:
| Prefix | Category | Description |
|--------|----------|-------------|
| feat: | Features | New features or capabilities |
| fix: | Bug Fixes | Bug fixes and corrections |
| build: | Build & Dependencies | Version updates, dependency changes, build configuration |
| refactor: | Refactoring | Code restructuring without behavior changes |
| test: | Tests | Test additions or modifications |
| chore: | Chores | Maintenance tasks, cleanup |
| docs: | Documentation | Documentation updates |
| perf: | Performance | Performance improvements |
| ci: | CI/CD | Continuous integration/deployment changes |
| style: | Style | Code style, formatting |
| revert: | Reverts | Reverting previous changes |
If no recognized prefix is found, use the first word or classify as "Other".
For worker component releases only, read the current versions from workers/pyproject.toml:
Extract the versions of these packages:
azure-functions-runtimeazure-functions-runtime-v1azure-functions (with Python version conditions)Important: The azure-functions SDK has different versions for different Python version ranges. Extract all variants.
Using PowerShell:
$pyprojectContent = Get-Content workers/pyproject.toml -Raw
# Extract runtime versions (single version each)
$runtimeMatch = [regex]::Match($pyprojectContent, 'azure-functions-runtime==([^;"\s]+)')
$runtimeV1Match = [regex]::Match($pyprojectContent, 'azure-functions-runtime-v1==([^;"\s]+)')
# Extract all azure-functions SDK versions with their Python version conditions
$sdkMatches = [regex]::Matches($pyprojectContent, '"azure-functions==([^;"]+);\s*([^"]+)"')
$sdkVersions = @()
foreach ($match in $sdkMatches) {
$version = $match.Groups[1].Value
$condition = $match.Groups[2].Value
$sdkVersions += @{Version=$version; Condition=$condition}
}
Using Python:
import re
with open('workers/pyproject.toml', 'r') as f:
content = f.read()
# Extract runtime versions
runtime = re.search(r'azure-functions-runtime==([^;"\s]+)', content)
runtime_v1 = re.search(r'azure-functions-runtime-v1==([^;"\s]+)', content)
# Extract all azure-functions versions with Python conditions
sdk_pattern = r'"azure-functions==([^;"]+);\s*([^"]+)"'
sdk_matches = re.findall(sdk_pattern, content)
# sdk_matches = [(version, condition), ...]
# Example: [('1.24.0', "python_version < '3.10'"), ('1.25.0b4', "python_version >= '3.10' and python_version < '3.13'")]
Format for output:
Store these versions for inclusion in the output.
Note: Skip this step for runtime v1 and runtime v2 releases.
For worker component releases only, classify each PR by which Python versions it affects:
Python version classification rules:
workers/ directory → General/Build sectionworkers/azure_functions_worker/workers/proxy_worker/workers/ that affect both (e.g., workers/pyproject.toml, workers/README.md)workers/pyproject.toml to determine which Python version range uses this versionazure-functions==2.0.0; python_version >= '3.13', then this PR is Python 3.13+ onlyazure-functions==1.25.0b4; python_version >= '3.10' and python_version < '3.13', then this PR is Python <= 3.12 onlyClassification logic:
for pr in filtered_prs:
files = get_pr_files(pr.number)
# Check if it's a worker version update
if re.match(r'[Uu]pdate\s+version\s+to\s+4\.\d+\.\d+', pr.title):
pr.section = 'worker_version'
continue
# Check if PR touches workers/ directory at all
touches_workers = any(f.startswith('workers/') for f in files)
if not touches_workers:
# General/build changes that don't touch worker code
pr.section = 'general'
continue
# Check if it's an SDK version update
if 'Update Python SDK Version' in pr.title:
version = extract_version_from_title(pr.title)
pyproject = read_pyproject()
python_range = find_python_range_for_sdk_version(pyproject, version)
if '3.13' in python_range and '3.10' not in python_range:
pr.python_versions = ['3.13+']
elif '3.10' in python_range or '3.12' in python_range:
pr.python_versions = ['<=3.12']
else:
pr.python_versions = ['<=3.12', '3.13+']
else:
# Regular file-based classification for worker code
affects_legacy = any(f.startswith('workers/azure_functions_worker/') for f in files)
affects_proxy = any(f.startswith('workers/proxy_worker/') for f in files)
affects_workers_general = any(
f.startswith('workers/') and
not f.startswith('workers/azure_functions_worker/') and
not f.startswith('workers/proxy_worker/')
for f in files
)
if affects_legacy and not affects_proxy and not affects_workers_general:
pr.python_versions = ['<=3.12']
elif affects_proxy and not affects_legacy and not affects_workers_general:
pr.python_versions = ['3.13+']
else:
# Both directories or workers/ general files
pr.python_versions = ['<=3.12', '3.13+']
Notes:
workers/ general files (e.g., workers/pyproject.toml) appear in both Python sectionsworkers/ at all go in the "General" sectionOrganize filtered PRs into categories based on their prefix.
Recommended category order for output:
For Build & Dependencies category only, deduplicate PRs that update the same package multiple times:
Package patterns to check:
Update Python SDK Version to X.Y.Z → package: azure-functionsUpdate Python Runtime Version to X.Y.Z → package: azure-functions-runtimeUpdate version to X.Y.Z (runtime v1) → package: azure-functions-runtime-v1Update azurefunctions-extensions-<name> version to X.Y.Z → package: azurefunctions-extensions-<name>Update <package-name> version to X.Y.Z → package: <package-name>Deduplication logic:
azure-functionsazure-functions-runtimeazure-functions-runtime-v1azurefunctions-extensions-<name>Example:
Input PRs (Build & Dependencies):
- Update Python SDK Version to 1.24.0b4 (#1755)
- Update Python SDK Version to 1.25.0b2 (#1798)
- Update Python SDK Version to 1.25.0b3 (#1822)
- Update azurefunctions-extensions-blob version to 1.1.1 (#1762)
- Update azurefunctions-extensions-blob version to 1.1.2 (#1821)
After deduplication:
- Update Python SDK Version to 1.25.0b3 (#1822) ✓ (highest PR for azure-functions)
- Update azurefunctions-extensions-blob version to 1.1.2 (#1821) ✓ (highest PR for extensions-blob)
Removed:
- #1755, #1798 (older azure-functions updates)
- #1762 (older extensions-blob update)
Implementation approach:
# Pseudocode
package_updates = {}
for pr in build_dependency_prs:
package_name = extract_package_name(pr.title)
if package_name:
if package_name not in package_updates or pr.number > package_updates[package_name].number:
package_updates[package_name] = pr
# Keep only the deduplicated PRs
deduplicated_prs = list(package_updates.values())
Note: Only apply deduplication to PRs that match version update patterns. Keep other build/dependency PRs as-is.
Generate clean, human-readable release notes in Markdown format.
For worker releases:
# Release Notes
## General
### Features
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
### Bug Fixes
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
### Build & Dependencies
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
[... other categories ...]
## Worker Version
* <title without prefix> ([#<number>](<link>)) - @<author>
## Python <= 3.12
### Features
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
### Bug Fixes
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
### Build & Dependencies
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
### Refactoring
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
### Tests
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
### Chores
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
## Python 3.13+
### Features
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
### Bug Fixes
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
### Build & Dependencies
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
### Refactoring
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
### Tests
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
### Chores
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
## Runtime and SDK Versions
azure-functions-runtime==<version>
azure-functions-runtime-v1==<version>
azure-functions==<version> (Python < 3.10)
azure-functions==<version> (Python 3.10-3.12)
azure-functions==<version> (Python 3.13+)
For runtime v1/v2 releases:
# Release Notes
## Features
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
## Bug Fixes
* <title without prefix> ([#<number>](<link>)) - @<author>
* ...
[... other categories ...]
Formatting rules:
workers/ directory (optional - only if PRs exist)azure_functions_worker/proxy_worker/fix: → )#number@usernameSave the formatted release notes to a temporary markdown file for easy copying:
File location:
$env:TEMP\release-notes-<component>.md/tmp/release-notes-<component>.mdExample:
release-notes-worker.mdrelease-notes-runtime-v1.mdrelease-notes-runtime-v2.mdUsing PowerShell:
$component = "worker" # or "runtime-v1" or "runtime-v2"
$outputPath = Join-Path $env:TEMP "release-notes-$component.md"
$releaseNotes | Out-File -FilePath $outputPath -Encoding utf8
Write-Host "Release notes saved to: $outputPath"
Using Python:
import tempfile
import os
component = "worker" # or "runtime-v1" or "runtime-v2"
temp_dir = tempfile.gettempdir()
output_path = os.path.join(temp_dir, f"release-notes-{component}.md")
with open(output_path, 'w', encoding='utf-8') as f:
f.write(release_notes)
print(f"Release notes saved to: {output_path}")
After saving:
PR without recognizable prefix:
PR affects multiple components:
Large PR lists (>30 PRs):
Authentication errors:
gh auth statusPR not found:
User provides:
Component: worker
PRs:
* fix: fix protobuf import by @user1 in https://github.com/org/repo/pull/100
* feat: add new feature for proxy worker by @user2 in https://github.com/org/repo/pull/101
* fix: legacy worker bug fix by @user3 in https://github.com/org/repo/pull/105
* build: update version to 4.40.0 by @user4 in https://github.com/org/repo/pull/102
* build: update Python SDK Version to 1.24.0b4 by @user5 in https://github.com/org/repo/pull/103
* build: update Python SDK Version to 1.25.0b2 by @user6 in https://github.com/org/repo/pull/150
* build: update Python SDK Version to 1.25.0b3 by @user7 in https://github.com/org/repo/pull/160
* build: update pyproject for azure-functions 2.x structure by @user8 in https://github.com/org/repo/pull/200
Agent:
setup.cfg (root level, not in workers/) → General sectionworkers/proxy_worker/handler.py → Python 3.13+ onlyworkers/azure_functions_worker/version.py + title matches \"Update version to 4.X.X\" → Worker Version sectionworkers/pyproject.toml (SDK updates) → Check version in pyprojectworkers/azure_functions_worker/dispatcher.py → Python <= 3.12 only.github/workflows/ci.yml (not in workers/) → General sectionproxy_worker/ → Python 3.13+ only\n - PR #102: Title \"Update version to 4.40.0\" → Worker Version section\n - PR #103, #150, #160: SDK updates - check pyproject.toml:\n - If version 1.25.0b3 is for Python 3.10-3.12 → Python <= 3.12 only\n - PR #105: Only azure_functions_worker/ → Python <= 3.12 only\n - PR #200: CI file, not in workers/ → General section\n10. Deduplicates version updates:\n - Package azure-functions: PRs #103, #150, #160\n - Keeps only #160 (highest PR number)\n - Removes #103 and #150\n11. Reads workers/pyproject.toml to extract versions:\n - azure-functions-runtime==1.1.0\n - azure-functions-runtime-v1==1.1.0\n - azure-functions==1.24.0 (Python < 3.10)\n - azure-functions==1.25.0b4 (Python 3.10-3.12)\n - azure-functions==2.0.0 (Python 3.13+)\n12. Groups by section and category, then outputs:\n\nmarkdown\n# Release Notes\n\n## General\n\n### Bug Fixes\n* Fix protobuf import (#100) - @user1\n\n### Build & Dependencies\n* Update pyproject for azure-functions 2.x structure (#200) - @user8\n\n## Worker Version\n* Update version to 4.40.0 (#102) - @user4\n\n## Python <= 3.12\n\n### Bug Fixes\n* Legacy worker bug fix (#105) - @user3\n\n### Build & Dependencies\n* Update Python SDK Version to 1.25.0b3 (#160) - @user7\n\n## Python 3.13+\n\n### Features\n* Add new feature for proxy worker (#101) - @user2\n\n## Runtime and SDK Versions\nazure-functions-runtime==1.1.0\nazure-functions-runtime-v1==1.1.0\nazure-functions==1.24.0 (Python < 3.10)\nazure-functions==1.25.0b4 (Python 3.10-3.12)\nazure-functions==2.0.0 (Python 3.13+)\n\n\nNote: \n- PR #100 and #200 in General section (don't touch workers/ directory)\n- PR #102 in Worker Version section (matches \"Update version to 4.X.X\")\n- PR #105 only in Python <= 3.12 (only affects azure_functions_worker/)\n- PR #101 only in Python 3.13+ (only affects proxy_worker/)\n- PR #160 only in Python <= 3.12 (SDK version 1.25.0b3 is for Python 3.10-3.12)\n- PRs #103 and #150 were excluded due to deduplication\n- Runtime and SDK Versions section is at the ENDUser provides:
Component: runtime v2
PRs:
* fix: fix protobuf import by @user1 in https://github.com/org/repo/pull/100
* feat: add new feature by @user2 in https://github.com/org/repo/pull/101
* build: update version to 1.0.0b1 by @user3 in https://github.com/org/repo/pull/110
* build: update version to 1.0.0b2 by @user4 in https://github.com/org/repo/pull/120
* build: update version to 1.1.0b1 by @user5 in https://github.com/org/repo/pull/130
* build: update azurefunctions-extensions-blob version to 1.1.1 by @user6 in https://github.com/org/repo/pull/140
* build: update azurefunctions-extensions-blob version to 1.1.2 by @user7 in https://github.com/org/repo/pull/145
* build: update Python SDK Version to 1.24.0 by @user8 in https://github.com/org/repo/pull/150
Agent:
setup.cfg (root level) → all components ✓runtimes/v2/handler.py → runtime v2 ✓runtimes/v2/version.py → runtime v2 ✓runtimes/v2/pyproject.toml (extensions-blob) → runtime v2 ✓workers/pyproject.toml (azure-functions) → worker-specific only ✗ (exclude)azure-functions-runtime-v2: PRs #110, #120, #130 → Keeps only #130azurefunctions-extensions-blob: PRs #140, #145 → Keeps only #145# Release Notes
## Features
* Add new feature ([#101](link)) - @user2
## Bug Fixes
* Fix protobuf import ([#100](link)) - @user1
## Build & Dependencies
* Update version to 1.1.0b1 ([#130](link)) - @user5
* Update azurefunctions-extensions-blob version to 1.1.2 ([#145](link)) - @user7
Notes:
azure-functions-runtime-v2 updates)azurefunctions-extensions-blob update)azure-functions SDK update)gh auth login)worker, runtime v1, or runtime v2)$env:TEMP\release-notes-<component>.md (Windows) or /tmp/release-notes-<component>.md (Linux/Mac)Take azure/generate-release-notes from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.