# GitOps Guide: Building and Releasing AgentOps Toolkit
This guide is a comprehensive instruction manual for engineers working on the **agentops-accelerator** project. It covers the full GitOps lifecycle - from setting up your development environment, through the branching model and CI pipeline, to staging and production releases.
## Table of Contents
- [1. GitOps Principles](#1-gitops-principles)
- [2. Branching Model](#2-branching-model)
- [3. Development Environment Setup](#3-development-environment-setup)
- [4. Development Workflow](#4-development-workflow)
- [5. CI Pipeline (Continuous Integration)](#5-ci-pipeline-continuous-integration)
- [6. Versioning with setuptools-scm](#6-versioning-with-setuptools-scm)
- [7. Staging Pipeline (TestPyPI)](#7-staging-pipeline-testpypi)
- [8. End-to-End Pipeline Testing](#8-end-to-end-pipeline-testing)
- [9. Production Release Pipeline (PyPI)](#9-production-release-pipeline-pypi)
- [10. Infrastructure Setup](#10-infrastructure-setup)
- [11. Workflow File Reference](#11-workflow-file-reference)
- [12. Release Checklist](#12-release-checklist)
- [13. Troubleshooting](#13-troubleshooting)
## 1. GitOps Principles
AgentOps follows GitOps practices where **git is the single source of truth** for both code and operational state:
- **Declarative configuration** - All pipeline behavior is defined in YAML workflow files checked into the repository.
- **Version-controlled releases** - Every release is traceable to a git tag. No manual version edits.
- **Automated pipelines** - Pushing branches or tags triggers the corresponding workflow automatically.
- **Environment gates** - Production deployment requires explicit human approval via GitHub Environments.
- **Immutable artifacts** - Built packages are uploaded once and reused across pipeline stages (no rebuilds between TestPyPI and PyPI).
## 2. Branching Model
AgentOps uses a modified [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) strategy:
```
main ← always production-ready, receives merges from release/* branches
│
develop ← integration branch, all feature PRs target here
│
├── feature/* ← individual features branched from develop
│
└── release/* ← release preparation, branched from develop when ready to ship
```
### Branch Purposes
| Branch | Purpose | Who creates | Merges into |
| ---------------- | -------------------------------------------------------------------- | ---------------- | ----------------------------- |
| `main` | Production-ready code. Every commit here should be a tagged release. | Maintainers only | - |
| `develop` | Integration branch. All feature work flows through here. | - | `main` (via release branches) |
| `feature/*` | Individual features, bug fixes, or improvements. | Any contributor | `develop` |
| `release/v0.X.Y` | Release stabilization and staging. Triggers TestPyPI pipeline. | Maintainers | `main` |
### Branch Lifecycle
```
1. feature/my-change ──PR──→ develop (contributor)
2. develop ──branch──→ release/v0.2.0 (maintainer, when ready to release)
3. release/v0.2.0 ──PR──→ main (maintainer, after staging validates)
4. main ──tag──→ v0.2.0 (maintainer, triggers production release)
5. main ──merge──→ develop (maintainer, sync the tag back)
6. release/v0.2.0 ──delete── (maintainer, cleanup)
```
### Branch Protection Rules (Recommended)
Configure these in **Settings → Branches → Branch protection rules**:
| Branch | Rules |
| ----------- | ------------------------------------------------------------------------ |
| `main` | Require PR, require status checks (CI), require approvals, no force push |
| `develop` | Require PR, require status checks (CI), no force push |
| `release/*` | Require status checks (Staging pipeline), no force push |
## 3. Development Environment Setup
### Prerequisites
- Python 3.11 or later
- [uv](https://docs.astral.sh/uv/) (recommended) or pip
- Git with access to the repository
### First-Time Setup
```bash
# 1. Clone the repository
git clone https://github.com/Azure/agentops.git
cd agentops
# 2. Install uv (if not already installed)
# macOS/Linux:
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
# 3. Install the project and dev dependencies
uv sync --group dev
# 4. Verify the installation
uv run agentops --version
uv run pytest tests/ -x -q
```
### Alternative Setup (pip)
```bash
python -m venv .venv
# Windows:
.venv\Scripts\Activate.ps1
# macOS/Linux:
source .venv/bin/activate
pip install -e .
pip install pytest
agentops --version
python -m pytest tests/ -x -q
```
### Verify Your Setup
After installation, these commands should all succeed:
```bash
# CLI works
agentops --version # Shows version like 0.1.3.dev6
agentops --help # Shows available commands
# Tests pass
uv run pytest tests/ -x -q # All tests should pass
# Version from git
python -m setuptools_scm # Shows version derived from git tags
```
## 4. Development Workflow
### Creating a Feature
```bash
# 1. Start from the latest develop
git checkout develop
git pull origin develop
# 2. Create your feature branch
git checkout -b feature/my-new-feature
# 3. Make changes, commit, push
# ... edit files ...
uv run pytest tests/ -x -q # Run tests before committing
git add .
git commit -m "feat: add my new feature"
git push origin feature/my-new-feature
# 4. Open a PR targeting develop
# GitHub will run the CI pipeline automatically
```
### PR Requirements
Before your PR can be merged to `develop`:
1. **CI pipeline passes** - lint + tests across OS/Python matrix
2. **Code review approved** - at least one reviewer
3. **Architecture rules followed** - see [CONTRIBUTING.md](../CONTRIBUTING.md)
4. **Tests included** - unit tests in `tests/unit/`, integration tests if needed
5. **CHANGELOG updated** - add entry under the appropriate versioned section for user-visible changes
### After Your PR is Merged
```bash
# Sync your local develop
git checkout develop
git pull origin develop
# Delete your feature branch
git branch -d feature/my-new-feature
```
## 5. CI Pipeline (Continuous Integration)
The CI pipeline runs on **every push and PR** to `main` or `develop`.
**Workflow file**: `.github/workflows/ci.yml`
### Jobs
| Job | What it does | Runs on |
| --- | --- | --- |
| **lint** | `ruff check` (linting) + `mypy` (type checking, soft-fail) | Ubuntu, Python 3.11 |
| **test** | `pytest tests/` with JUnit XML output | Matrix: 2 OS × 3 Python versions |
| **coverage** | `pytest --cov` with XML coverage report | Ubuntu, Python 3.13 (after tests pass) |
| **publish-dev** | Build package + publish to TestPyPI (develop pushes only) | Ubuntu, Python 3.12 (after lint + test pass) |
| **verify-dev** | Install from TestPyPI + smoke test (develop pushes only) | Ubuntu, Python 3.12 (after publish-dev) |
The `publish-dev` and `verify-dev` jobs only run on pushes to `develop` (not on PRs). Every merged PR automatically produces an installable dev build on TestPyPI with a version like `0.1.3.dev12`.
### Test Matrix
| OS | Python 3.11 | Python 3.12 | Python 3.13 |
| ------- | ----------- | ----------- | ----------- |
| Ubuntu | ✅ | ✅ | ✅ |
| Windows | ✅ | ✅ | ✅ |
### What CI Catches
- Syntax and style issues (ruff)
- Type errors (mypy, non-blocking)
- Test failures across platforms
- Import errors or missing dependencies
- Regression in exit code behavior
### Viewing CI Results
1. Go to the **Actions** tab → find the CI run for your PR
2. Click into a failing job to see the error
3. Download test result artifacts if needed
## 6. Versioning with setuptools-scm
AgentOps uses [setuptools-scm](https://github.com/pypa/setuptools-scm) for **fully automatic versioning**. There is **no `version` field in `pyproject.toml`** - the version is derived from git tags at build time.
### How It Works
setuptools-scm reads your git history and computes the version:
| Git state | Example version | Explanation |
| --------------------------------------------- | --------------- | ----------------------------- |
| Exactly on tag `v0.2.0` | `0.2.0` | Clean release version |
| 3 commits after `v0.2.0` | `0.2.1.dev3` | Dev version, 3 commits ahead |
| 10 commits after `v0.1.2` on `release/v0.2.0` | `0.1.3.dev10` | Dev version on release branch |
### Configuration
In `pyproject.toml`:
```toml
[build-system]
requires = ["setuptools>=68", "wheel", "setuptools-scm>=8"]
[project]
dynamic = ["version"] # Version comes from setuptools-scm, not a static field
[tool.setuptools_scm]
local_scheme = "no-local-version" # Strips +hash suffix (PyPI rejects local versions)
```
### Checking the Version
```bash
# From the installed CLI
agentops --version
# From setuptools-scm directly
python -m setuptools_scm
# From Python code
python -c "from agentops import __version__; print(__version__)"
```
### Rules
- **Never add `version = "..."` to `pyproject.toml`** - this will conflict with setuptools-scm.
- **Tags must follow PEP 440** - use `v0.2.0`, not `release-0.2.0` or `0.2.0`.
- **`fetch-depth: 0`** is required in CI checkout steps - setuptools-scm needs the full git history.
- **`pip install -e .` requires `.git`** - editable installs need the git directory present (standard for development).
## 7. Staging Pipeline (TestPyPI)
The staging pipeline validates a release candidate by publishing to TestPyPI and verifying the installed package works.
**Workflow file**: `.github/workflows/staging.yml`
**Trigger**: Push to any `release/*` branch
### Pipeline Flow
```mermaid
flowchart TD
push(["push to release/v0.2.0"])
build["_build
tests + package
Version: 0.2.1.dev3 (setuptools-scm)"]
publish["publish-testpypi
Upload to TestPyPI (staging environment)
Uses TEST_PYPI_TOKEN secret"]
verify["verify-testpypi
Install from TestPyPI in fresh environment
agentops --version / --help / init"]
push --> build --> publish --> verify
```
### What Gets Validated
1. **Tests pass** - the full test suite runs before building
2. **Package builds** - setuptools-scm generates the correct version, wheel and sdist are created
3. **Package uploads** - the built artifacts successfully upload to TestPyPI
4. **Package installs** - `pip install` from TestPyPI resolves all dependencies
5. **CLI works** - `agentops --version` and `--help` run without errors
6. **Init works** - `agentops init` creates the expected workspace files
### Iterating on a Release Branch
If staging fails, fix the issue and push again:
```bash
# On your release/v0.2.0 branch
# ... fix the issue ...
git add .
git commit -m "fix: correct packaging issue"
git push origin release/v0.2.0
# Staging pipeline re-runs automatically
```
Each push generates a new dev version (e.g. `0.2.1.dev4`, `0.2.1.dev5`), so there are no version conflicts on TestPyPI. The `skip-existing: true` flag also prevents failures if the same version is re-uploaded.
### Manual Verification (Optional)
After the staging pipeline passes, you can manually test the package:
```bash
# Install the specific dev version from TestPyPI
pip install "agentops-accelerator==0.2.1.dev3" \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/
agentops --version
agentops --help
# Test init in a temp directory
cd $(mktemp -d)
agentops init
ls .agentops/
```
> **Note**: `--extra-index-url https://pypi.org/simple/` is required so that dependencies (typer, pydantic, ruamel.yaml) resolve from the real PyPI.
## 8. End-to-End Pipeline Testing
Before cutting a real release, you can validate the entire pipeline end-to-end using a disposable test branch and tag. This is especially useful when:
- You've modified any workflow file (`_build.yml`, `staging.yml`, `release.yml`)
- You've changed `pyproject.toml` build configuration
- You've updated setuptools-scm settings
- A new engineer wants to understand the release process hands-on
### 8.1 Test the Staging Pipeline
#### Step 1: Create a Test Release Branch
From the branch that contains your workflow changes (or from `develop`):
```bash
git checkout develop # or your feature branch with workflow changes
git pull origin develop
git checkout -b release/v0.0.0-test
git push origin release/v0.0.0-test
```
This triggers the `staging.yml` workflow automatically.
#### Step 2: Monitor the Pipeline
1. Go to **Actions** tab → find the **Staging** workflow run for `release/v0.0.0-test`
2. Watch all 3 jobs:
```
Job 1: build / build → Should tests pass? Package build?
Job 2: publish-testpypi → Does TestPyPI upload succeed?
Job 3: verify-testpypi → Can the package install and run?
```
3. Click into each job to inspect step-level output
4. If a job fails, read the logs, fix the issue, push again:
```bash
# Fix and re-push
git add .
git commit -m "fix: correct workflow issue"
git push origin release/v0.0.0-test
# Pipeline re-runs automatically
```
#### Step 3: Verify on TestPyPI (Optional)
Confirm the test package appeared on TestPyPI:
```bash
# Check the version that was published
python -m setuptools_scm
# Install and test manually
pip install "agentops-accelerator==$(python -m setuptools_scm)" \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/
agentops --version
agentops --help
# Test init
cd $(mktemp -d)
agentops init
ls .agentops/
```
#### Step 4: Clean Up the Test Branch
```bash
# Delete remote branch
git push origin --delete release/v0.0.0-test
# Switch back and delete local branch
git checkout develop
git branch -d release/v0.0.0-test
```
### 8.2 Test the Full Release Pipeline (Including PyPI Approval Gate)
> **Warning**: This will publish a test version to PyPI if you approve it. Only do this if you want to validate the full production flow. You can cancel at the approval gate to skip the actual PyPI publish.
#### Step 1: Create a Test Tag
From `develop` or your feature branch:
```bash
git tag v0.0.0-test.1
git push origin v0.0.0-test.1
```
This triggers the `release.yml` workflow.
#### Step 2: Monitor the Pipeline
1. Go to **Actions** tab → find the **Release** workflow run for `v0.0.0-test.1`
2. Watch the jobs execute in sequence:
```
Job 1: build / build ✅ Tests + build
Job 2: publish-testpypi ✅ Upload to TestPyPI
Job 3: verify-testpypi ✅ Install + smoke test
Job 4: publish-pypi ⏸️ PAUSES - waiting for approval
Job 5: github-release ⏳ Waiting for Job 4
```
3. At the `publish-pypi` step, you have two choices:
- **Approve** - publishes to real PyPI (use only if you want to test the full flow)
- **Reject** - cancels the remaining jobs without publishing to PyPI
#### Step 3: Inspect the Approval Gate
1. Click on the **Release** workflow run
2. The `publish-pypi` job shows a yellow "Waiting" badge
3. Click **Review deployments**
4. Select the **release** environment
5. Choose **Reject** to cancel without publishing, or **Approve and deploy** to continue
This validates that the environment protection rules and reviewer requirements work correctly.
#### Step 4: Clean Up
```bash
# Delete the test tag (remote and local)
git push origin --delete v0.0.0-test.1
git tag -d v0.0.0-test.1
# If a GitHub Release was created, delete it manually:
# Go to Releases → find v0.0.0-test.1 → Delete
```
If you approved the PyPI publish, the test version (`0.0.0.test1`) will exist on PyPI permanently (PyPI versions cannot be deleted, only yanked). This is harmless but visible.
### 8.3 Quick E2E Test Summary
| What to test | Command | What to watch |
| ------------------- | -------------------------------------------------------------------- | --------------------------------- |
| Staging only | `git push origin release/v0.0.0-test` | 3 jobs: build → TestPyPI → verify |
| Full release (safe) | `git push origin v0.0.0-test.1` then **reject** at approval | 4 jobs run, approval gate works |
| Full release (real) | `git push origin v0.0.0-test.1` then **approve** | All 5 jobs, package on PyPI |
| Cleanup (branch) | `git push origin --delete release/v0.0.0-test` | Branch removed |
| Cleanup (tag) | `git push origin --delete v0.0.0-test.1 && git tag -d v0.0.0-test.1` | Tag removed |
### 8.4 Testing Workflow Changes on a Feature Branch
If you're modifying the workflow files on a feature branch (not yet merged to `develop`), you can still test them:
```bash
# Your workflow changes are on feature/my-ci-changes
git checkout feature/my-ci-changes
# Create a test release branch directly from your feature branch
git checkout -b release/v0.0.0-test
git push origin release/v0.0.0-test
# GitHub Actions uses the workflow files from the pushed branch,
# so your modifications are what actually runs
```
This is useful because GitHub Actions reads workflow files from the branch being pushed, not from `main` or `develop`. Your modified workflows execute immediately without needing to merge first.
After testing:
```bash
# Clean up
git push origin --delete release/v0.0.0-test
git checkout feature/my-ci-changes
git branch -d release/v0.0.0-test
```
## 9. Production Release Pipeline (PyPI)
The production pipeline publishes a final release to PyPI and creates a GitHub Release.
**Workflow file**: `.github/workflows/release.yml`
**Trigger**: Push a `v*` tag (e.g. `v0.2.0`)
### Pipeline Flow
```mermaid
flowchart TD
tag(["push tag v0.2.0"])
build["_build
tests + package
Version: 0.2.0 (clean, from tag)"]
publishTest["publish-testpypi
Final TestPyPI upload (clean version)"]
verifyTest["verify-testpypi
Smoke test from TestPyPI"]
publishPypi{{"publish-pypi ⏸
PAUSES - requires approval
Uses PYPI_TOKEN
environment: release"}}
ghRelease["github-release
Creates GitHub Release with artifacts
Auto-generated release notes"]
tag --> build --> publishTest --> verifyTest --> publishPypi --> ghRelease
classDef gate fill:#fff3cd,stroke:#856404,color:#000;
class publishPypi gate;
```
### Step-by-Step: Cutting a Release
#### Step 1: Cut the Release (One-Click)
1. Go to the **Actions** tab → select **Cut Release** workflow
2. Click **Run workflow**
3. Enter the version (e.g. `0.2.0`) - no `v` prefix
4. Click **Run workflow**
The workflow automatically:
- Creates `release/v0.2.0` from `develop`
- Updates `CHANGELOG.md` (adds versioned section `[0.2.0] - YYYY-MM-DD`)
- Pushes the branch (triggers [staging pipeline](#7-staging-pipeline-testpypi))
- Opens a PR: `release/v0.2.0` → `main`
> **Alternative (manual)**: If you prefer to create the release branch locally:
> ```bash
> git checkout develop && git pull origin develop
> git checkout -b release/v0.2.0
> # Edit CHANGELOG.md manually
> git commit -m "chore: prepare release 0.2.0"
> git push origin release/v0.2.0
> ```
#### Step 2: Wait for Staging
The branch push triggers the staging pipeline automatically. Wait for it to pass.
#### Step 3: Monitor Staging
1. Go to **Actions** tab → find the **Staging** workflow run
2. Verify all 3 jobs pass:
- ✅ `build / build` - tests pass, package builds
- ✅ `publish-testpypi` - uploaded to TestPyPI
- ✅ `verify-testpypi` - installed and smoke-tested
If any job fails, fix the issue on the release branch and push. The pipeline re-runs automatically.
#### Step 4: Merge to Main
Create a PR from `release/v0.2.0` → `main` (or use the one already opened by Cut Release):
1. Go to GitHub → **Pull Requests** → **New Pull Request**
2. Base: `main` ← Compare: `release/v0.2.0`
3. Title: `Release v0.2.0`
4. Get the required reviews and merge
#### Step 5: Tag the Release
```bash
git checkout main
git pull origin main
git tag v0.2.0
git push origin v0.2.0
```
This triggers the [production release pipeline](#8-production-release-pipeline-pypi).
#### Step 6: Approve the PyPI Publish
1. Go to **Actions** tab → find the **Release** workflow run for `v0.2.0`
2. The pipeline will run through build → TestPyPI → verify
3. At the `publish-pypi` job, it pauses with **"Waiting for review"**
4. Click **Review deployments** → select the **release** environment → **Approve and deploy**
5. The package publishes to PyPI
6. The `github-release` job creates a GitHub Release with the built artifacts and auto-generated release notes
#### Step 7: Post-Release Cleanup
```bash
# Sync the tag back to develop
git checkout develop
git pull origin develop
git merge main
git push origin develop
# Delete the release branch (remote and local)
git push origin --delete release/v0.2.0
git branch -d release/v0.2.0
```
#### Step 8: Verify the Published Package
```bash
# Install from PyPI
pip install agentops-accelerator==0.2.0
# Verify
agentops --version # Should show 0.2.0
agentops --help
```
Check the published package:
- PyPI: https://pypi.org/project/agentops-accelerator/0.2.0/
- GitHub Release: https://github.com/Azure/agentops/releases/tag/v0.2.0
## 10. Infrastructure Setup
This section covers one-time setup required before the pipelines can run.
### 10.1 GitHub Environments
Create two environments in **Settings → Environments → New environment**:
#### `staging` Environment
- **Purpose**: Controls access to TestPyPI publishing
- **Protection rules**: None required (auto-deploys), or add reviewers for extra safety
- **Secrets**:
| Secret | Value | How to get it |
| ----------------- | ------------------ | --------------------------------------------------------------------------------- |
| `TEST_PYPI_TOKEN` | TestPyPI API token | [test.pypi.org/manage/account/token](https://test.pypi.org/manage/account/token/) |
#### `release` Environment
- **Purpose**: Controls access to production PyPI publishing
- **Protection rules**: **Required reviewers** - add at least one team member who must approve
- **Deployment branches**: Optionally restrict to `main` branch and `v*` tags
- **Secrets**:
| Secret | Value | How to get it |
| ------------ | --------------------------------------------- | ----------------------------------------------------------------------- |
| `PYPI_TOKEN` | PyPI API token (scoped to `agentops-accelerator`) | [pypi.org/manage/account/token](https://pypi.org/manage/account/token/) |
### 10.2 PyPI and TestPyPI Accounts
#### TestPyPI (Staging)
1. Go to [test.pypi.org/account/register](https://test.pypi.org/account/register/)
2. Create an account (separate from PyPI - different databases)
3. Go to [test.pypi.org/manage/account/token](https://test.pypi.org/manage/account/token/)
4. Create an API token (scope: entire account for first upload, then project-scoped after)
5. Add the token as `TEST_PYPI_TOKEN` secret in the GitHub `staging` environment
> **Note**: TestPyPI and PyPI are completely separate systems with separate accounts, tokens, and namespaces. An account on one does not grant access to the other.
#### PyPI (Production)
1. Go to [pypi.org/account/register](https://pypi.org/account/register/) or log in
2. Go to [pypi.org/manage/account/token](https://pypi.org/manage/account/token/)
3. Create an API token scoped to the `agentops-accelerator` project
4. Add the token as `PYPI_TOKEN` secret in the GitHub `release` environment
### 10.3 First-Time Package Registration
The first time you publish to TestPyPI or PyPI, the project name (`agentops-accelerator`) is registered automatically. After the first upload:
- Scope your API tokens to the specific project for better security
- Add collaborators/maintainers on the PyPI/TestPyPI project page if needed
## 11. Workflow File Reference
All workflow files are in `.github/workflows/`:
### `ci.yml` - Continuous Integration
```
Trigger: push to develop, PR to develop
Flow: lint → test (matrix) → coverage
+ on develop push: publish-dev → verify-dev (TestPyPI)
Purpose: Quality gate for all code changes; auto-publish dev builds
```
Key detail: `publish-dev` and `verify-dev` only run on pushes to `develop` (not PRs). Every merge to develop produces a dev version on TestPyPI (e.g. `0.1.3.dev12`) via setuptools-scm. PRs to `main` are not covered by CI because they come from `release/*` branches which are already validated by the staging pipeline.
### `_build.yml` - Reusable Build
```
Trigger: workflow_call (called by staging.yml and release.yml)
Flow: checkout (full history) → uv sync → pytest → uv build → upload artifact
Purpose: Single source of truth for the build process
```
Key detail: Uses `fetch-depth: 0` to ensure setuptools-scm has full git history for version derivation.
### `staging.yml` - Staging Pipeline
```
Trigger: push to release/* branches, or workflow_dispatch
Flow: _build → publish-testpypi → verify-testpypi
Purpose: Validate release candidates before production
```
Key details:
- `skip-existing: true` allows re-pushes without upload failures
- Verify step uses a retry loop (5 attempts, 30s apart) for TestPyPI index propagation
- Smoke tests cover `--version`, `--help`, and `agentops init`
### `release.yml` - Production Release
```
Trigger: push v* tags, or workflow_dispatch
Flow: _build → publish-testpypi → verify-testpypi → publish-pypi (approval) → github-release
Purpose: Publish to PyPI and create GitHub Release
```
Key details:
- `publish-pypi` uses `environment: release` which requires reviewer approval
- `github-release` uses `gh release create` with `--generate-notes` for automatic release notes
- Built artifacts (.whl, .tar.gz) are attached to the GitHub Release
### `cut-release.yml` - Cut Release (Manual Dispatch)
```
Trigger: workflow_dispatch (manual button in Actions tab)
Input: version - semver string (e.g. 0.2.0)
Flow: validate → create release branch → update CHANGELOG → push → open PR
Purpose: One-click release branch creation from develop
```
Key details:
- Creates `release/v` branch from `develop`
- Automatically updates `CHANGELOG.md` - inserts a versioned section `[] - ` at the top
- Opens a PR from `release/v` → `main` with a checklist
- The branch push triggers `staging.yml` automatically
- Fails safely if the branch already exists
- Does NOT auto-tag or auto-publish - tagging remains a manual, intentional step
## 12. Release Checklist
Use this checklist when cutting a release:
**Preparation**
- [ ] All intended features/fixes are merged to `develop`
- [ ] `CHANGELOG.md` has entries for all user-visible changes under the appropriate versioned section
- [ ] Tests pass locally: `uv run pytest tests/ -x -q`
- [ ] Version from setuptools-scm looks correct: `python -m setuptools_scm`
**Staging**
- [ ] Release branch created via **Cut Release** workflow (or manually)
- [ ] CHANGELOG automatically updated with version and date
- [ ] Staging pipeline passes: build + TestPyPI + verify (all 3 green)
- [ ] PR opened: `release/v0.X.Y` → `main`
**Production**
- [ ] PR from `release/v0.X.Y` → `main` created and approved
- [ ] PR merged to `main`
- [ ] Version tag created and pushed: `v0.X.Y`
- [ ] Release pipeline runs: build + TestPyPI + verify pass
- [ ] PyPI publish approved in GitHub Actions
- [ ] GitHub Release created with artifacts
- [ ] Published package verified: `pip install agentops-accelerator==0.X.Y`
**Cleanup**
- [ ] `main` merged back to `develop`
- [ ] Release branch deleted (remote and local)
- [ ] CHANGELOG is ready for new entries
## 13. Troubleshooting
### Build Failures
| Problem | Cause | Solution |
| ---------------------------------------- | ----------------------------------- | --------------------------------------------- |
| `setuptools_scm` can't determine version | Shallow clone (missing git history) | Ensure `fetch-depth: 0` in checkout step |
| Version shows `0.0.0` locally | Not in a git repo or no tags exist | Run `git tag v0.0.1` to create an initial tag |
| `ModuleNotFoundError` in tests | Dependencies not installed | Run `uv sync --group dev` |
| Tests fail on Windows but pass on Linux | Path separator issues | Use `pathlib.Path`, not string concatenation |
### TestPyPI Issues
| Problem | Cause | Solution |
| --------------------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| Upload fails with 403 | Invalid or expired token | Regenerate `TEST_PYPI_TOKEN` and update the GitHub secret |
| Upload fails with "already exists" | Same version previously uploaded | Normal - `skip-existing: true` handles this. If you need a new upload, push another commit to increment the dev version |
| Install fails with "no matching distribution" | Package not yet indexed | The verify job retries automatically (5 attempts, 30s apart). If persistent, check TestPyPI status |
| Install fails with dependency errors | Dependency not on TestPyPI | Verify `--extra-index-url https://pypi.org/simple/` is present |
### PyPI Issues
| Problem | Cause | Solution |
| ------------------------------------------ | ----------------------------------------- | -------------------------------------------------------------- |
| Publish step stuck on "Waiting for review" | Normal - requires approval | A designated reviewer must approve in the Actions UI |
| Upload fails with 403 | Invalid `PYPI_TOKEN` | Regenerate the token on pypi.org and update the GitHub secret |
| Version already exists on PyPI | Tag points to an already-released version | PyPI versions are immutable. You must use a new version number |
### Git and Version Issues
| Problem | Cause | Solution |
| ------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------ |
| Wrong version in built package | Tag not on the expected commit | Verify with `git log --oneline --decorate` that the tag is where you expect |
| `pip install -e .` fails | `.git` directory missing | Editable installs need git history for setuptools-scm. Clone the repo, don't just download a zip |
| Merge conflicts between release and develop | Normal for concurrent work | Resolve conflicts on the release branch before merging to main |
### Environment and Permissions
| Problem | Cause | Solution |
| --------------------------------- | ----------------------------------- | ---------------------------------------------------------------------- |
| "Environment not found" error | GitHub Environment not created | Create `staging` and `release` environments in Settings → Environments |
| "Secret not found" error | Secret not added to the environment | Add secrets to the specific environment, not repository-level secrets |
| Reviewer can't approve deployment | Not listed as required reviewer | Update the environment's required reviewers list |
## Architecture Diagram
```mermaid
flowchart TD
feat["feature/*"] -->|PR| develop(["develop"])
develop --> ci["CI (ci.yml)
lint + test + coverage
publish-dev → TestPyPI (dev version)"]
develop --> cut{{"Cut Release (cut-release.yml)
manual dispatch - enter version"}}
cut --> rel(["release/v0.2.0"])
rel --> stagingBuild["_build
test + build"]
stagingBuild --> stagingTest["TestPyPI publish"]
stagingTest --> stagingVerify["Verify install"]
rel -->|PR| main(["main"])
main -->|tag| tag(["v0.2.0"])
tag --> relBuild["_build"]
relBuild --> relTest["TestPyPI"]
relTest --> relVerify["Verify"]
relVerify --> relPypi{{"PyPI
(approval)"}}
relPypi --> relGh["GitHub Release"]
main -->|merge back| develop
subgraph Staging["Staging (staging.yml)"]
stagingBuild
stagingTest
stagingVerify
end
subgraph Release["Release (release.yml)"]
relBuild
relTest
relVerify
relPypi
relGh
end
classDef gate fill:#fff3cd,stroke:#856404,color:#000;
class cut,relPypi gate;
```