# ToolHive Release Workflow Reference

Detailed documentation of the ToolHive release workflow chain.

## Workflow Overview

```
┌─────────────────────────┐
│  create-release-pr.yml  │  ← Manual trigger (workflow_dispatch)
│  (bump_type input)      │
└───────────┬─────────────┘
            │ Creates PR with version bumps
            ▼
┌─────────────────────────┐
│  PR Review & Merge      │  ← Human review
│  (commit: Release vX.Y.Z)│
└───────────┬─────────────┘
            │ VERSION file changes on main
            ▼
┌─────────────────────────┐
│ create-release-tag.yml  │  ← Auto trigger (push to main, VERSION changed)
│                         │
└───────────┬─────────────┘
            │ Creates tag + GitHub Release
            ▼
┌─────────────────────────┐
│     releaser.yml        │  ← Auto trigger (release published)
│                         │
└───────────┬─────────────┘
            │
            ├── verify-release (tag matches VERSION)
            ├── release-binaries (GoReleaser, cosign, SBOM)
            ├── image-build-and-push (container images)
            ├── publish-helm (Helm charts to GHCR)
            └── update-docs-website (trigger docs PR)
```

## Workflow 1: create-release-pr.yml

**Trigger**: Manual (`workflow_dispatch`)

**Input**: `bump_type` (patch | minor | major)

**What it does**:

1. Uses `stacklok/releaseo` action to:
   - Read current version from `VERSION` file
   - Bump version according to `bump_type`
   - Update `VERSION` file
   - Update additional files:
     - `deploy/charts/operator-crds/Chart.yaml` (version, appVersion)
     - `deploy/charts/operator/Chart.yaml` (version, appVersion with `v` prefix)
     - `deploy/charts/operator/values.yaml` (operator.image, toolhiveRunnerImage, vmcpImage)
   - Run `helm-docs --chart-search-root=deploy/charts`
   - Create PR with branch `release/vX.Y.Z`

**Output**: PR number and URL

## Workflow 2: create-release-tag.yml

**Trigger**: Push to `main` that changes `VERSION` file

**What it does**:

1. Read and validate VERSION file (must be valid semver)
2. Verify commit came from release PR:
   - Commit message matches `Release vX.Y.Z` or merge from `release/vX.Y.Z`
   - Version in commit message matches VERSION file
3. Check if tag already exists (skip if so)
4. Create annotated git tag `vX.Y.Z`
5. Push tag using a GitHub App installation token (required to trigger downstream workflows; `GITHUB_TOKEN`-authored events do not)
6. Create GitHub Release with auto-generated notes

**Requirements**:
- GitHub App installed on the repo with `contents: write` permission
- `RELEASE_APP_CLIENT_ID` repository **variable** (the app's Client ID)
- `RELEASE_APP_PRIVATE_KEY` repository **secret** (the app's private key in PEM)

## Workflow 3: releaser.yml

**Trigger**: `release` event with type `published`

**Jobs**:

### verify-release
- Confirms git tag matches VERSION file content

### compute-build-flags
- Extracts commit SHA, date, version, tree-state for ldflags

### release-binaries
- Builds test binary and verifies version matches tag
- Runs GoReleaser for all platforms (linux, darwin, windows × amd64, arm64)
- Signs with cosign (keyless)
- Generates SBOMs with Syft
- Publishes to:
  - GitHub Release assets
  - Homebrew tap (`HOMEBREW_TAP_GITHUB_TOKEN`)
  - Winget (`WINGET_GITHUB_TOKEN`)

### image-build-and-push
- Builds container images for:
  - thv
  - thv-operator
  - thv-proxyrunner
  - vmcp
- Signs images with cosign
- Pushes to GHCR

### publish-helm
- Verifies tag matches VERSION
- Packages and pushes Helm charts to GHCR

### update-docs-website
- Triggers PR to docs repository with new version

### notify-release-failure
- Sends Slack notification if any job fails

**Requirements**:
- `GITHUB_TOKEN` (automatic)
- `HOMEBREW_TAP_GITHUB_TOKEN`
- `WINGET_GITHUB_TOKEN`
- `DOCS_REPO_DISPATCH_TOKEN`
- `SLACK_TOOLHIVE_RELEASE_WEBHOOK_URL`

## Files Updated by Release

| File | Fields Updated |
|------|----------------|
| `VERSION` | Full version number (e.g., `0.9.0`) |
| `deploy/charts/operator-crds/Chart.yaml` | `version`, `appVersion` |
| `deploy/charts/operator/Chart.yaml` | `version`, `appVersion` (with `v` prefix) |
| `deploy/charts/operator/values.yaml` | `operator.image`, `operator.toolhiveRunnerImage`, `operator.vmcpImage` |
| `deploy/charts/*/README.md` | Regenerated by helm-docs |

## Semantic Versioning Guidelines

| Change Type | Version Bump | Example |
|-------------|--------------|---------|
| Breaking API changes | Major | 0.8.3 → 1.0.0 |
| Removed features | Major | 0.8.3 → 1.0.0 |
| New features (backward compatible) | Minor | 0.8.3 → 0.9.0 |
| New CLI commands | Minor | 0.8.3 → 0.9.0 |
| New CRD fields | Minor | 0.8.3 → 0.9.0 |
| Bug fixes | Patch | 0.8.3 → 0.8.4 |
| Performance improvements | Patch | 0.8.3 → 0.8.4 |
| Dependency updates | Patch | 0.8.3 → 0.8.4 |
| Documentation only | Patch | 0.8.3 → 0.8.4 |

## Troubleshooting

### Reference already exists when creating release PR

If a previous Create Release PR run failed after creating the branch but before opening the PR, the branch (e.g. `release/v0.11.1`) is left behind. The next run fails with "Reference already exists" because releaseo cannot create the same branch again.

**Fix**: The workflow now includes a cleanup step that deletes the target release branch before running releaseo, allowing retries to succeed. Simply re-run the workflow.

### PR not triggering create-release-tag

- Ensure commit message matches expected pattern: `Release vX.Y.Z`
- Check that VERSION file was actually modified in the PR

### Tag creation fails

- Tag may already exist: `git tag | grep vX.Y.Z`
- Release GitHub App may be uninstalled, or the `RELEASE_APP_CLIENT_ID` variable / `RELEASE_APP_PRIVATE_KEY` secret may be missing or stale
- App may lack `contents: write` permission on the repo

### Releaser workflow fails

- Check VERSION file matches the tag
- Verify all required secrets are configured
- Check Slack for failure notification with details

### Helm chart publish fails

- Verify tag matches VERSION file
- Check GHCR authentication
