Release Workflow
Ship a feature branch to production: PR → CI → merge → tag → PyPI → docs.
Version argument: The version number is passed as the first argument (e.g., /release-workflow 1.0.2). All steps below use this version.
Prerequisites
Before running this workflow, verify:
- You are on a feature branch (not main)
- All work is committed (
git statusis clean) - All tests pass locally:
PYTHONPATH=src pytest tests/ - The version number follows semver and is greater than the current version in
pyproject.toml
Step 1: Bump Version
Update pyproject.toml with the release version:
# In pyproject.toml, change:
version = "<VERSION>"
Commit:
git add pyproject.toml
git commit -m "chore: bump version to <VERSION>"
Step 2: Push Branch & Create PR
git push -u origin HEAD
gh pr create --title "<PR title from branch context>" --body "$(cat <<'EOF'
## Summary
<summarize changes from git log>
## Test plan
- [ ] All unit tests pass
- [ ] All integration tests pass
- [ ] All E2E tests pass
- [ ] Lint passes (ruff check + format)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
Step 3: Wait for CI
gh pr checks --watch
All checks must pass. If any fail:
- Read the failure logs:
gh run view <run-id> --log-failed - Fix the issue on the feature branch
- Push and re-check
Step 4: Merge PR
gh pr merge --squash --delete-branch
Step 5: Tag Release
git checkout main
git pull
git tag v<VERSION>
git push origin v<VERSION>
This triggers .github/workflows/release.yml which:
- Runs full CI gate (validate, lint, security, unit tests, integration tests)
- Builds sdist + wheel
- Publishes to PyPI via trusted publisher
- Creates GitHub Release with auto-generated changelog
Step 6: Verify Release Pipeline
gh run list --workflow=release.yml --limit=1
gh run watch
Wait for completion. If it fails:
- CI gate failure: check test output
- Build failure: check
uv build/twine checkoutput - PyPI publish failure: check trusted publisher config in repo settings
Step 7: Deploy Versioned Docs
The docs pipeline does NOT auto-trigger from releases created by GitHub Actions. You must manually dispatch with the version number:
gh workflow run docs.yml -f version=<VERSION>
This deploys the docs as version <VERSION> and sets it as the latest alias.
Verify:
gh run list --workflow=docs.yml --limit=1
gh run watch
Step 8: Final Verification — Remote
# PyPI package available
pip index versions crux-cli
# GitHub release page
gh release view v<VERSION>
# Docs site
open https://crux-cli.github.io/crux/
Step 9: Smoke Test — Fresh Install
Verify the release works end-to-end by reinstalling from scratch on this system.
9a. Uninstall current crux
uv tool uninstall crux-cli
Do NOT remove ~/.crux/ — we want to verify the new version works with existing data.
9b. Reinstall from PyPI via the install script
curl -LsSf https://raw.githubusercontent.com/crux-cli/crux/main/install.sh | sh
9c. Verify version number matches the release
crux version
Expected output should contain <VERSION>. If it shows an older version, the PyPI publish may not have propagated yet — wait 1-2 minutes and retry:
uv tool upgrade crux-cli
crux version
9d. Test a change that shipped with this release
Pick one user-facing change from the release and verify it works. Examples:
- If a new command was added: run it and check output
- If a command was renamed: verify old name fails and new name works
- If auth was changed: run
crux mcp authand check the output
# Example: verify the CLI restructuring shipped
crux mcp --help # should show add/remove/list/search/upgrade/auth/status
crux project --help # should show create/install/uninstall/sync/status
crux task --help # should show run/init/list/clean
# Example: verify a specific feature
crux mcp auth # should show auth status table (not "not implemented")
crux doctor # should NOT mention secrets
If the smoke test fails, investigate whether it's a packaging issue (wrong files included) or a code issue (bug in the release).
Rollback
If something goes wrong after tagging but before PyPI publish:
git tag -d v<VERSION>
git push origin :refs/tags/v<VERSION>
If already published to PyPI, you cannot delete the version. Instead:
# Revert the merge on main
git revert <merge-commit-sha>
git push
# Publish a patch version with the fix
Notes
- The release pipeline requires a PyPI trusted publisher configured in GitHub repo settings
- The docs pipeline uses
mikefor versioned documentation - Squash merge keeps main history clean — one commit per feature
- Always verify locally before creating the PR