Changelog automation
When a PR (develop to main) is opened against the main branch, the generation ladder produces release notes and builds the changelog automatically. If one step fails, it falls to the next step down, and the last resort, commit analysis, completes without any AI (#455, #566).
Overview
| Feature | Description |
|---|---|
| AI analysis | The ladder (PR body, Copilot, external AI, commit analysis) analyzes the changes automatically |
| Fallback ladder | On failure it falls to the next step; the last resort is commit analysis with no AI dependency (#566) |
| Category classification | Sorts changes into Features, Bug Fixes, and so on |
| Dual format | JSON (data) + Markdown (readable) |
| PR title automation | Renames the title to Deploy YYYYMMDD-vX.X.X |
Automation flow
develop push
│
│ (the workflow does not create the release PR automatically)
▼
develop → main release PR created ← created by a person or the /pro-changelog-deploy skill
│
▼
RELEASE-CHANGELOG workflow (pull_request_target: opened)
│
├─ head guard: skip everything if the PR head is not develop
├─ rename the PR title to "🚀 Deploy YYYYMMDD-vX.Y.Z" right away
├─ obtain release notes (provider ladder)
├─ update CHANGELOG.json / generate CHANGELOG.md
├─ version finalization commit
└─ PR automerge
│
▼
main push (release merge)
│
├─ README-VERSION-UPDATE
├─ PLUGIN-VERSION-SYNC
└─ CICD deployNote: two common misunderstandings
VERSION-CONTROLdoes not react to develop pushes. It is a safety net that runs only on direct pushes to main (see Version control).- No workflow creates the release PR automatically. Pushing develop does not open a PR, so use the
/pro-changelog-deployskill or open the PR yourself.
Former name: this workflow was renamed from
PROJECT-COMMON-AUTO-CHANGELOG-CONTROLtoPROJECT-COMMON-RELEASE-CHANGELOGin v4.3.0. If the old file is still present, thenpx projectopsupdate neutralizes it automatically (see the NPX wizard guide).
Release note provider ladder
The release note generator is chosen with metadata.template.options.changelog.provider in version.yml (#455).
metadata:
template:
options:
changelog:
provider: "commit" # copilot | openai | gemini | claude | groq | mistral | ollama | commit | coderabbit
# base_url: "http://localhost:11434/v1" # ollama only (required)When unset, the provider is coderabbit if .coderabbit.yaml exists at the repository root, and commit otherwise (#821). An explicit value always wins. The workflow and the /pro-changelog-deploy skill resolve it with the same rule.
| provider | Method | Requirements |
|---|---|---|
commit (unset, no .coderabbit.yaml) | Commit message analysis (commit.py). If an AI key is registered, AI is tried first | None. No AI or network dependency; the last resort |
coderabbit (unset, .coderabbit.yaml present) | Uses the CodeRabbit Summary already in the PR body (no waiting, #566). Otherwise AI key, then commit | CodeRabbit app installed on the repository |
copilot | Copilot CLI (copilot.py) | None. Works with the job's permissions: copilot-requests: write and GITHUB_TOKEN only (no API key; default model openai/gpt-4o-mini). Consumes Premium Requests |
openai / gemini / claude | OpenAI-compatible API (openai_compatible.py) | MODEL_API_KEY secret |
ollama | OpenAI-compatible API (self-hosted) | changelog.base_url required (default model qwen2.5) |
Fallback order (.github/scripts/changelog_providers/ladder.py):
commitruns (AI if a key is registered), then commitcopilotruns Copilot, then (AI if a key is registered), then commitopenai/gemini/claude/groq/mistral/ollamarun that provider, then commitgithub-aiis discontinued (2026-07-30); it is not called and is absorbed into commitcoderabbitdoes not wait. If the body already has a summary, it is used as is; otherwise (AI if a key is registered), then commit
When a fallback happens, a PR comment records which provider took over. Because the commit provider always finishes, the release notes are never empty.
Test: python -m pytest .github/scripts/test/test_changelog_providers.py
Output files
CHANGELOG.json
A structured data format for programmatic access.
{
"versions": [
{
"version": "1.2.3",
"date": "2026-01-12",
"categories": {
"Features": [
"Add new login feature"
],
"Bug Fixes": [
"Fix sign-up error"
]
}
}
]
}CHANGELOG.md
A Markdown format that is easy for people to read.
# Changelog
## [1.2.3] - 2026-01-12
### Features
- Add new login feature
### Bug Fixes
- Fix sign-up errorUsing changelog_manager.py
Basic commands
# update from the CodeRabbit summary
python3 .github/scripts/changelog_manager.py update-from-summary
# regenerate Markdown
python3 .github/scripts/changelog_manager.py generate-md
# extract the release notes of one version
python3 .github/scripts/changelog_manager.py export --version 1.2.3 --output release_notes.txtThe three subcommands above (
update-from-summary/generate-md/export) are all that is supported.
Category classification
The selected provider classifies the changes automatically.
| Category | Description | Example keywords |
|---|---|---|
| Features | New features | feat, add, new |
| Bug Fixes | Bug fixes | fix, bug, resolve |
| Documentation | Documentation changes | docs, readme |
| Performance | Performance improvements | perf, optimize |
| Refactoring | Code refactoring | refactor, clean |
| Tests | Test additions and changes | test, spec |
| Chores | Other work | chore, build |
Automatic PR title formatting
The workflow renames the develop to main PR title automatically (a workflow step does this, not CodeRabbit, and it always runs regardless of the provider).
Before:
Merge develop into mainAfter:
🚀 Deploy 20260112-v1.2.3- Format:
🚀 Deploy {YYYYMMDD}-v{version}. It includes the rocket emoji and has no summary text after it.
Workflow
PROJECT-COMMON-RELEASE-CHANGELOG.yaml
on:
pull_request_target:
types: [opened]
branches: ["main"]Trigger conditions:
- Runs only when a PR is opened against the main branch.
synchronize(an extra push to the PR) is not a trigger, so pushing to the PR again does not re-run it. - To re-run, close the PR and open a new one, or use the retrigger in
/pro-changelog-deploy. - It uses
pull_request_target, notpull_request, so it runs against the base (main) and can access secrets. - Head guard: if the PR head branch is not
develop, the whole pipeline is skipped. This prevents a feature PR whose base was set to main by mistake (main is the default branch).
What it does:
- Reads the changelog provider from version.yml (when unset: coderabbit if
.coderabbit.yamlexists, otherwise commit) - If the PR body already has release notes (a Summary), uses them as is. No provider is waited for (#566)
- If there is no Summary, the fallback-summary job runs the provider ladder (ladder.py)
- Parses the Summary/release notes, updates CHANGELOG.json, generates CHANGELOG.md
- Commits the changes (version finalization commit)
- Automerges the PR
CodeRabbit integration (provider=coderabbit, or unset with .coderabbit.yaml)
Requirements
- CodeRabbit app installed on the repository
.coderabbit.yamlconfiguration (optional)
Summary format
The Summary format CodeRabbit leaves on the PR:
## Summary by CodeRabbit
### Changes
- Added new login feature
- Fixed signup validation bug
### Files Changed
- src/auth/login.ts
- src/auth/signup.tsDual parsing strategy
Both the legacy and the current CodeRabbit formats are supported.
Current format
## Summary by CodeRabbit
<details>
<summary>Changes</summary>
...
</details>Legacy format
**Summary**
- Change 1
- Change 2Troubleshooting
Changelog not generated
Symptom: CHANGELOG is not updated even after the PR is merged
What to check:
- Check
options.changelog.providerinversion.yml(if coderabbit, check that CodeRabbit left a Summary) - Check that the
_GITHUB_PAT_TOKENsecret is set (for openai-family providers, also checkMODEL_API_KEY) - In the Actions log, check which provider the fallback-summary job finished with (look for the
PROVIDER=<winner>output)
Summary parsing failure
Symptom: "Could not parse CodeRabbit summary" error
Fix:
- Check the CodeRabbit Summary format in the PR comments
- Check that the HTML tags are not broken
PR automerge failure
Symptom: The changelog was generated but the PR was not merged
What to check:
- Check the branch protection rules
- Check the PAT token permissions (repo, workflow)
- Check Repository Settings → Actions permissions
Manual update
If the automation fails, you can update by hand.
# 1. Edit CHANGELOG.json by hand
# 2. Regenerate Markdown
python3 .github/scripts/changelog_manager.py generate-md
# 3. Commit & push
git add CHANGELOG.json CHANGELOG.md
git commit -m "docs: update changelog"
git pushThe version finalization commit and follow-up workflow triggers
The version finalization commit that RELEASE-CHANGELOG creates when it automerges the develop to main release PR does not include [skip ci]. This commit becomes the main HEAD, so the workflows triggered by a main push (NPM-PUBLISH, README-VERSION-UPDATE, PLUGIN-VERSION-SYNC, and each project's deploy CICD) must trigger automatically on release. (A push to the default branch is the deploy trigger.)
Why there is no infinite loop:
- The
VERSION-CONTROLsafety net recognizes this release commit throughpaths-ignore(version.yml)andrelease_guard(which detects whether the commit includes a version.yml change) and skips the re-bump. README-VERSION-UPDATEandPLUGIN-VERSION-SYNCkeep[skip ci]on the follow-up commits they create, so they do not retrigger each other.RELEASE-CHANGELOGuses thepull_request_target: [opened]trigger, so it does not re-run on the commit it creates (synchronizeis not a trigger).
Note: some workflows do use a develop push as a trigger (
PROJECT-TEMPLATE-CI,PROJECT-COMMON-TEMPLATE-UTIL-VERSION-SYNC,PROJECT-FLUTTER-CI,PROJECT-REACT-CI,PROJECT-SPRING-NEXUS-CI). All of them are CI (verification) only and do not touch the version or CHANGELOG, so they are unrelated to the release loop.
If you add
[skip ci]back to the version finalization commit, every deploy and sync stops on each release. Do not add it.