# Releasing OneUptime How a OneUptime release is cut, made green and shipped - for the people and the agents who do it. Releases run as a loop: as soon as one is out, the next one is cut from `main`, once `main` has pull requests the last one did not ship. The other release guides cover one part each: [the mobile apps](packages/MobileApp/RELEASING.md), [the infrastructure agent's package managers](agents/InfrastructureAgent/RELEASING.md), [Slack](packages/Common/Server/Utils/Workspace/Slack/RELEASING.md) and [Microsoft Teams](packages/Common/Server/Utils/Workspace/MicrosoftTeams/RELEASING.md), [the GitHub Apps](packages/Common/Server/Utils/CodeRepository/GitHub/RELEASING.md), [the cloud marketplaces](packages/Common/Server/Utils/Marketplace/RELEASING.md) and [self-hosted marketplace editions](packages/Common/Server/Utils/Marketplace/SELF_HOSTED_RELEASING.md). Release notes are written from pull request titles; AGENTS.md's "Release notes" section says how to title a change. ## What a release is - `VERSION` holds the version (`14.0.106`). Every internal `package.json` and the `package-lock.json` beside it carry the same version; `node Scripts/Install/SyncPackageVersions.js` writes them and `node Scripts/Install/SyncPackageVersions.js --check` (run by the commit hook and by the release run) fails on any drift. - A push to the `release` branch starts the release run, `.github/workflows/release.yml` ("Push Release Images to Docker Hub and GitHub Container Registry"). It refuses a version that already has a GitHub release. - The run builds and pushes every image (Docker Hub and GHCR), runs the release E2E suites (SaaS, self-hosted, enterprise) against those images, publishes the npm packages and the Terraform provider, then: - `push-release-tags` ("Push release tags before GitHub release") moves the images' `release` tags, - `helm-chart-deploy` publishes both Helm charts, - `draft-github-release` writes the notes (`Scripts/Release/GenerateReleaseNotes.js`) and `finalize-github-release` ("Publish GitHub release") publishes the GitHub release with its assets, - then the MCP Registry, the coding-agent plugin and the infrastructure agent's apt/yum, Homebrew and winget packages. - A run takes 8 to 10 hours: its jobs queue behind the organization's concurrent-job limit. - OneUptime has released when the GitHub release for the version is published (`gh release view 14.0.106`), the run's `push-release-tags`, `helm-chart-deploy` and `finalize-github-release` jobs succeeded, and every other job either succeeded or failed for a reason written down (see "When the release run fails"). `release` only ever moves by merging a release branch into it. ## The release loop The example is 14.0.106; use the next patch version after the latest GitHub release (`gh release list --limit 1`). ### 1. Cut the release branch from `main` ```bash git fetch origin main git checkout -b release-14.0.106 origin/main ``` Name it `release-`. (`release/` cannot exist beside the `release` branch: git keeps `release` as a file where `release/` would be a folder.) ### 2. Bump the version ```bash echo 14.0.106 > VERSION node Scripts/Install/SyncPackageVersions.js git add -A VERSION '*package.json' '*package-lock.json' git commit -m "chore: bump version to 14.0.106" ``` The commit is `VERSION` and the package.json / package-lock.json pairs, about 55 files, nothing else. ### 3. Push it and open the release pull request into `release` ```bash git push origin release-14.0.106 gh pr create --base release --head release-14.0.106 --title "Release 14.0.106" ``` Every workflow that runs on `pull_request` runs on it - Compile, Common Jobs, Common Test, App Test (with its Dashboard Offline UI groups), Enterprise Edition Test, Postgres Schema Drift, Ops Config Test, the Terraform workflows, Build, the agents' tests, the OpenStack and eBPF E2E suites and the rest - on the merge of the branch into `release`, which is exactly the tree the release run will build. Its checks are the release's gate. In the description, say what the release is (`main` at which commit), and keep a list of the fixes made in the branch. Keep the title starting with "Release": the release notes leave a pull request into `release` with such a title out (`RELEASE_GATE_TITLE` in `Scripts/Release/GenerateReleaseNotes.js`) and list each fix made in the branch as a commit of its own, under its title's type and area. ### 4. Give the release the runners ```bash bash Scripts/Release/PrioritizeReleaseRuns.sh release-14.0.106 ``` Leave it running (in its own terminal, or in the background with its output in a log) until the release has shipped. Once a minute it cancels every queued or running workflow run whose branch is neither `release-14.0.106` nor `release`: pushes to `main`, other pull requests, scheduled runs. The organization's concurrent-job limit is shared, and a full CI run on `main` after every merge kept the release's own jobs queued for hours. Cancelled runs can be re-run once the release is out; nobody waits on `main`'s CI while a release is in flight. ### 5. Fix every red check in the release branch Each failure is fixed in `release-14.0.106` itself, at its root cause, and pushed there as soon as it is verified locally - do not wait for CI between fixes; every push restarts the pull request's checks with all fixes so far. - Read a failure with `gh run view --job --log-failed` (save it to a file and grep it - they run to hundreds of thousands of lines). - Find the commit that broke it (`git log -S`, `git log -p -- `, `git blame`) and what it meant to do. Usually a change was right and a test, fixture, guard, translation or doc was not updated with it; sometimes the change itself is wrong. Fix whichever side is wrong. - Never skip, delete, `.skip` or loosen a test, raise a limit or a timeout to get past it. A test changes only when it is out of date with an intended change, and then it checks the new behaviour. - Commit each fix on its own, titled as a release-note line (`test(terraform): ...`, `fix(ci): ...`, `fix(incidents): ...`), then `git fetch origin release-14.0.106 && git rebase origin/release-14.0.106 && git push origin HEAD:release-14.0.106`. - When many checks are red, split them by area (compile, lint, Postgres suites, Common Test, App Test, the browser suites, ...) between people or agents, each in a worktree of their own, all pushing to the release branch. Run single test files locally, not whole folders. - After every push to the release branch, merge the branch into `main`: ```bash bash Scripts/Release/MergeReleaseBranchIntoMain.sh release-14.0.106 ``` It computes the merge without a checkout, pushes "Merge release-14.0.106 into main", and does nothing when `main` already has the branch. So `main` gets each fix as soon as it exists, and the pull requests merged into `main` meanwhile are built on them. On a conflict it pushes nothing and prints the files: merge those by hand, keeping both sides' intent. The merges only go this way - the release branch never takes `main`'s new commits; whatever `main` merges after the cut waits for the next release. ### 6. Merge the release pull request when every check is green ```bash gh pr checks # every check passed or skipped gh pr merge --merge # --admin if the code-owner review is still missing ``` Merge it with a merge commit (not squash, not rebase): the merge's push to `release` starts the release run. ### 7. Merge the release branch into `main` right away As soon as the pull request is merged - not once the release run has finished - merge the branch into `main` one last time: ```bash bash Scripts/Release/MergeReleaseBranchIntoMain.sh release-14.0.106 ``` After the merges of step 5 this is usually only the last fixes, or nothing. Then `main` has the version bump and every fix the release ships, and the next release branch starts from them. ### 8. Follow the release run until OneUptime has released ```bash gh run list --workflow release.yml --branch release --limit 1 gh run watch gh release view 14.0.106 ``` Keep `PrioritizeReleaseRuns.sh` running through it. The release is out when "What a release is" above holds. #### When the release run fails - A job that failed for a transient reason (a registry timeout, a runner lost): `gh run rerun --failed`. - A failure in the code, before anything was published: fix it in the release branch, as in step 5 (which merges it into `main` too), and merge the branch into `release` again. The version has no GitHub release yet, so the new run goes ahead. - A failure after the GitHub release was published, in a job that only publishes elsewhere (a package manager): re-run it once its cause is fixed, or fix it in the next release. `Publish the infrastructure agent to winget` depends on Microsoft's review of the pull request it opens: see [agents/InfrastructureAgent/RELEASING.md](agents/InfrastructureAgent/RELEASING.md). - `publish-android-to-play-store` and `publish-ios-to-app-store` are skipped unless the run is dispatched with them turned on: [packages/MobileApp/RELEASING.md](packages/MobileApp/RELEASING.md). ### 9. Start the next release - if `main` has something new Once OneUptime has released, see whether any pull request has merged into `main` since the release was cut: ```bash git fetch origin main release git log --oneline --no-merges origin/release..origin/main ``` These are the commits on `main` that `release` does not have; the merges of the release branch into `main` add nothing of their own, so they are not listed. - Something listed: go back to step 1 with the next patch version, cut from `main` as it is now, and restart `PrioritizeReleaseRuns.sh` with the new branch's name. - Nothing listed: there is nothing to release. Stop `PrioritizeReleaseRuns.sh`, so `main` and pull requests get their CI again, and check again later; cut the next release when something has merged.