How to support RC builds and keep a grouped changelog for the final release?
I’m struggling to design a correct CI/release workflow using python-semantic-release together with Woodpecker CI, and I feel like I’m mixing concepts incorrectly.
My goals are:
- Accumulate multiple commits (feat/fix/chore/etc.)
- Have them appear under a single grouped version in the changelog (e.g. v2.1.0)
- Still be able to generate and use versioned RC builds (v2.1.0-rc.1, rc.2, etc.) for testing before release
- Maintain a clean and predictable CI/CD flow
What I tried
1. dev + main with semantic-release on both
- Feature branches → squash merge into dev
- Run semantic-release on dev → generates vX.Y.Z-rc.N
- Then merge dev → main and run semantic-release again
Problem:
- Each RC bumps version and updates the changelog
- When merging to main, commits are already “consumed”
- Final changelog is fragmented across multiple RC entries instead of grouped
2. Rebase + squash workflow
- Squash commits into dev
- Rebase dev onto main
Problem:
- Breaks git history
- semantic-release cannot properly detect previous tags
- Version calculation becomes incorrect (falls back to older versions)
3. Adding a release/* branch
- dev → release/x.y (merge commit)
- Run semantic-release on release/x.y → RC versions
- Merge release/x.y → main
Problem:
- RC releases still update changelog and consume commits
- Final release on main does not regroup all changes under one version
- Changelog remains split across multiple RC entries
Core issue
It seems like running semantic-release on prerelease branches (dev or release/*):
- “Consumes” commits by generating versions and changelog entries
- Prevents those commits from being grouped later in the final release on main
At the same time, I still need:
- Versioned artifacts for testing (e.g. Docker images for a FastAPI app)
- Ability to test individual features or combinations before a stable release
Additional constraint
If I stop using RC versions on dev:
- I lose structured versioning for test deployments
- I can only rely on commit SHA-based builds
- This makes version tracking less clear compared to semantic versions
What I want to achieve
An ideal workflow would:
- Allow continuous feature integration on dev
- Allow creation of versioned RC builds for testing (e.g. v2.1.0-rc.1)
- Ensure that when releasing to main, the changelog is fully grouped:
## v2.1.0
### Features
- feature A
- feature B
### Fixes
- fix C
and not split across multiple RC entries.
Questions
- What is the correct way to structure branches and CI with python-semantic-release to support both:
- RC builds
- grouped final changelog?
- Should semantic-release be:
- run only on main?
- or also on prerelease branches but with limited plugins (e.g. no changelog)?
- How should test deployments be handled properly?
- Use commit SHA-based images?
- Or rely on RC versions?
- Is there a recommended pattern for this (especially with Woodpecker CI), or am I fundamentally misusing semantic-release?
Tech stack
- python-semantic-release
- Woodpecker CI
- FastAPI app (Dockerized)
- Conventional commits
Any guidance or real-world examples would be highly appreciated 🙏
Configuration
Pretty basic config for now, I'm open to changes, perhaps I did something wrong
Semantic Release Configuration
[semantic_release]
commit_message = "{version} [skip ci]\n\nAutomatically generated by python-semantic-release"
commit_parser = "conventional"
tag_format = "v{version}"
[semantic_release.branches.main]
match = "(main|master)"
prerelease = false
[semantic_release.branches.dev]
match = "dev"
prerelease_token = "rc"
prerelease = true
.woodpecker.yaml
when:
event: push
branch:
- dev
- main
skip_clone: true
steps:
semantic-release:
image: python:3.14.3-slim
environment:
GH_TOKEN:
from_secret: cookie-github-token
commands:
- apt-get update && apt-get install -y git
- git clone "$CI_REPO_CLONE_URL" && cd "$CI_REPO_NAME" && git checkout "$CI_COMMIT_BRANCH"
- pip install python-semantic-release
- semantic-release -vv --config releaserc.toml version --skip-build --no-vcs-release
Additional context
I tried lots of things before but right now it looks like this
git log --oneline --decorate --graph --all -n 50
* 46896df (tag: v2.2.0, origin/main, origin/HEAD, main) 2.2.0 [skip ci]
* 33af00a Dev (#33)
|\
| * effc434 (HEAD -> feat/letssee, tag: v2.2.0-rc.2, origin/dev, dev) 2.2.0-rc.2 [skip ci]
| * 44893d2 fix: huh what the heck why not (#32)
| * e468fb7 (tag: v2.2.0-rc.1) 2.2.0-rc.1 [skip ci]
| * 0212f35 feat: trying something interesting (#30)
| | * 18a82a8 (origin/feature/diff, feature/diff) huh what the heck why not
| |/
| | * 0223404 (origin/feature/newone, feature/newone) lets try
| |/
| * 7682309 finale? [skip ci]
| |\
| |/
|/|
* | 1f992d4 (tag: v2.1.0) 2.1.0 [skip ci]
* | 8084339 feat: finale supposedly lmao (#28)
* | 13bea6f (tag: v2.0.1) 2.0.1 [skip ci]
* | 7c8651e fix: try fixie fix (#25)
* | df53a68 (tag: v2.0.0) 2.0.0 [skip ci]
* | df54475 Dev (#24)
|\ \
* | | db66d0c (tag: v1.0.0) 1.0.0 [skip ci]
* | | d3c9292 Dev (#19)
|\ \ \
| | | * 4f0ebbe Merge branch 'dev' of github.com:cookielevanna5/computer-networks into dev
| | | |\
| | | | * 8d6d7f8 (tag: v1.0.0-rc.3) feat: finale supposedly lmao (#28)
| | | | | * cab06a4 (origin/feature/finale, feature/finale) finale?
| | | | |/
| | | |/|
| | | * | 76003e1 Merge branch 'dev' of github.com:cookielevanna5/computer-networks into dev
| | | |\|
| | | | * bf46a02 lets see [skip ci] (#27)
| | | | * 6f491b5 (tag: v1.0.0-rc.2) fix: try fixie fix (#25)
| | | |/
| | |/|
| | | | * ef9b155 (origin/feature/ohno, feature/ohno) alalal
| | | |/
| | | * 743a365 Merge branch 'dev' of github.com:cookielevanna5/computer-networks into dev
| | | |\
| | | |/
| | |/|
| | * | 9ad36ac (tag: v1.0.0-rc.1) feat: completely oops (#23)
| | * | fcc6e5d fix: oops (#22)
| | | | * 947dc92 (origin/feature/oops, feature/oops) princewwwwww
| | | | * 26d1138 prince
| | | |/
| | | * ed883b7 Merge branch 'dev' of github.com:cookielevanna5/computer-networks into dev
| | | |\
| | | |/
| | |/|
| | * | f74d7f5 feat(wow)!: amazing wowowow (#21)
| | | | * 8c6ebe2 (origin/feature/another) Merge branch 'dev' into feature/another
| | | | |\
| | | |_|/
| | |/| |
| | * | | fd0a973 fix(semantic-release): lets see (#20)
| |/ / /
| | | * 7d01a1a (feature/another) fadiha rezah
| | |/
| | | * 2851a4b (origin/feature/see, feature/see) major_on_zero ssstrue
| | |/
| | * 019824b Merge branch 'dev' of github.com:cookielevanna5/computer-networks into dev
| | |\
| | |/
| |/|
| * | 57dfcf2 (tag: v0.1.0-rc.7) feat(conf): major_on_zero set to true (#18)
| | | * d5ee345 (origin/feature/major) major_on_zero true
| | |/
| | * 05f2d53 Merge branch 'dev' of github.com:cookielevanna5/computer-networks into dev
| | |\
| | |/
| |/|
| * | 6351115 (tag: v0.1.0-rc.6) feat(api)!: something incredibly different wowowow (#17)
| * | 4776420 (tag: v0.1.0-rc.5) feat(trying): interesting (#16)
| | | * 9d8a477 (origin/feature/sheraton) lolol 67
| | |/
| | | * 9fed372 (origin/feature/lol) lolol
| | |/
| | * 3226331 Merge branch 'dev' of github.com:cookielevanna5/computer-networks into dev
| | |\
| | |/
| |/|
| * | 80c90e7 (tag: v0.1.0-rc.4) fix(api): found something great in api wow (#15)
| | | * 33f14d0 (origin/feature/try) added distinguishing between stuff
| | | * 55a413e Merge branch 'dev' into feature/try
| | | |\
| | | |/
| | |/|
| | * | d0700ec Merge branch 'main' into dev
| | |\ \
| |_|/ /
|/| | |
| | * | 1cfb03a try lololo 67
| |/ /
| | * ad636e1 Merge branch 'main' into feature/try
| | |\
| |_|/
|/| |
How to support RC builds and keep a grouped changelog for the final release?
I’m struggling to design a correct CI/release workflow using python-semantic-release together with Woodpecker CI, and I feel like I’m mixing concepts incorrectly.
My goals are:
What I tried
1. dev + main with semantic-release on both
Problem:
2. Rebase + squash workflow
Problem:
3. Adding a release/* branch
Problem:
Core issue
It seems like running semantic-release on prerelease branches (dev or release/*):
At the same time, I still need:
Additional constraint
If I stop using RC versions on dev:
What I want to achieve
An ideal workflow would:
and not split across multiple RC entries.
Questions
Tech stack
Any guidance or real-world examples would be highly appreciated 🙏
Configuration
Pretty basic config for now, I'm open to changes, perhaps I did something wrong
Semantic Release Configuration
.woodpecker.yaml
Additional context
I tried lots of things before but right now it looks like this
git log --oneline --decorate --graph --all -n 50