Orca ORCA v0.174.2
Documentation navigation

Alpha and Beta Pre-releases

Sometimes you want to build and test a change before it is merged to main — for example, to deploy a bugfix to dev and verify it against real traffic while the PR is still in review. That is what the alpha and beta branches are for. They are pre-release channels: pushing to them cuts a pre-release (e.g. v1.2.4-alpha.1), which the release workflow containerizes and can deploy to an environment like dev.

Each branch maps to a release channel:

Branch Channel Tag format Example
main stable vX.Y.Z v1.2.3
alpha pre-release vX.Y.Z-alpha.N v1.2.4-alpha.1
beta pre-release vX.Y.Z-beta.N v1.2.4-beta.1
next release candidate vX.Y.Z-rc.N v1.3.0-rc.1

Versioning is driven by conventional commits via the create-release action (semantic-release under the hood). A fix: commit bumps the patch version, a feat: commit bumps the minor version. That distinction matters — see the rule below.

How to cut a pre-release

Cutting a pre-release is just a force-push of your current branch to the alpha (or beta) branch:

$ git push origin your-branch:alpha --force

The push triggers the repo's Release workflow, which:

  1. Runs semantic-release in dry-run mode to compute the next pre-release tag.
  2. Builds and pushes a container image tagged with that tag (e.g. v1.2.4-alpha.1).
  3. Creates the GitHub release and publishes the image to the configured environment(s).

Some repos wrap this push in a Makefile target so you don't have to remember the command:

$ make alpha-release
Force-pushing 'fix/wake-word-regression' -> origin/alpha
Note

The exact target name varies by repo — some have alpha-pre-release (which rebases onto the remote alpha branch instead of force-pushing). Check the repo's Makefile (make help usually lists the targets).

The golden rule: only fix commits on alpha and beta

Important

Every commit on alpha or beta must be a fix: commit. Never push feat: commits to a pre-release branch.

This isn't bureaucracy — it prevents version collisions.

First, how our create-release action picks versions (semantic-release under the hood): for the branch being released, it looks only at the tags merged into that branch (git tag --merged), takes the highest version among them, and bumps it according to the commit types since that tag. main therefore only ever counts stable tags merged into main — pre-release tags on alpha never affect what main releases. This is our policy, not a semantic-release limitation: stable releases stay correct either way.

The collision happens on the pre-release channel itself, because pre-release tags are permanent even though alpha is a throwaway branch:

  1. The latest stable release is v1.2.3, and you push feat: faster wake-word detection to alpha. This cuts the tag v1.3.0-alpha.1 — the 1.3.0 pre-release series now belongs to the alpha channel.
  2. In review, the change gets descoped and merges to main squashed into a fix: commit. main correctly releases v1.2.4 — but nothing will ever release a stable 1.3.0. The pre-release series is stranded: any further pre-release on alpha, even a fix, continues it (v1.3.0-alpha.2, v1.3.0-alpha.3, …), promising a minor version that will never exist.
  3. Sooner or later alpha gets reset to match main (that is what make alpha-release does — a force-push), and the old feat commit leaves the branch history. But the tag v1.3.0-alpha.1 survives, because tags outlive force-pushes.
  4. The next genuine feat: pushed to alpha computes its version against the stable tags now merged into the branch (v1.2.4), lands on v1.3.0-alpha.1 again, and finds the tag already exists. The release fails, and you are stuck hand-deleting tags.

With fix: commits only on the channel, every pre-release is patch-level, and patch slots are cheap: when the PR eventually merges to main — with its real feat: title if it's a feature — the channel's baseline moves past any stranded pre-release tags, so no future recomputation lands on a tag that already exists. A stranded minor series, on the other hand, is exactly what the next feat: on the channel recomputes. That asymmetry is why the rule exists.

So: if your pre-release build needs a feat, don't push it as a feat to alpha. You have two options:

  1. Merge to main and release properly. The feat gets the minor-version bump it deserves (v1.3.0), and no pre-release is needed at all. This is the default.
  2. Cut the pre-release from a fix squash — but keep the feat title on the PR. If the change genuinely must be alpha-tested before merge, squash it into a single fix: commit and push that to alpha/beta. The PR itself keeps its feat: title, so when it finally merges into main, it releases as the minor version it deserves (v1.3.0). The pre-release tag was only ever a test build — main ignores it.

What the two channels see doesn't have to match: the pre-release channel always sees fix: commits, and main sees the change's real type through the PR title. The only forbidden combination is the one above — a feat: commit on alpha or beta.

Keep the channels in sync

After anything merges to main and a stable release is cut, update alpha and beta to match main so their version history doesn't drift out of sync:

$ git push origin main:alpha --force-with-lease
$ git push origin main:beta  --force-with-lease

The push needs force: alpha and beta are force-pushed throwaway branches, so their tips are usually not ancestors of main and a plain push would be rejected as non-fast-forward. --force-with-lease protects you from overwriting someone else's in-flight pre-release build — check with your team before resetting a channel that may be mid-test.

Key takeaways

  • alpha/beta are throwaway pre-release channels for testing fixes before they land on main.
  • A pre-release is just a force-push to the channel branch (git push origin your-branch:alpha --force); some repos wrap it in make alpha-release.
  • Only fix: commits on pre-release branches — a feat strands a pre-release series that main will never release, and its surviving tag later collides with the next recomputed version.
  • A feat can still be pre-released: squash it into a fix: commit for the alpha/beta build, and keep the feat: title on the PR so the final merge to main releases the minor version it deserves.
  • After merging to main, push main to alpha/beta with --force-with-lease to keep the channels in sync.