Builds
Build branches
How an app's builds are organized into branches, which build each run uses, and how to manage branches from the CLI and the dashboard.
A branch is a named line of builds in one app, such as main, staging, or
feature-login. Every build belongs to one branch and never moves to another.
Test runs, sessions, and Atlas evidence record the branch of the build they used.
Each app has its own branches, and its first upload sets its default branch. Use separate apps for builds that differ in platform or configuration, such as bundle ID or backend environment, and branches for lines of work on the same app.
What branches are for
- Keep a source-of-truth branch current. Upload each new
mainorstagingbuild to that branch and test or explore it, from CI or a cloud AI agent automation, so its results and Atlas map follow the app you ship. See Keep a source-of-truth branch current. - Keep feature work apart. Builds you upload from a feature branch go to a
branch of the same name, so runs and links on
mainkeep usingmain's builds. - Test a chosen branch once, or save it so every run uses it. See Test a branch.
- Compare
mainwith a feature branch in Atlas, or by running the same workflow on both. See Compare two branches. - Hold a steady release candidate. Pin
stagingto one build. See Pin a release candidate.
Default branch and default build
The app's default branch is the branch a run uses when nothing else picks
one. A new app's default branch is the branch of its first upload, such as
main; until its first upload it's base. Pull request preview builds from the
Revyl GitHub and GitLab apps don't set it. Change it with
revyl app branch default main --app "My App" or Make default branch… in
App settings > Build branches. Changing it doesn't change saved choices
that name a branch.
Each branch has a default build, the build a run on the branch uses when it doesn't pick one:
| Default build | Uses | Set it with |
|---|---|---|
| Latest build | The branch's newest usable build. Every branch starts here. | revyl app branch unpin, or Change default build… > Latest build. Deleting a branch's pinned build also returns the branch to Latest build. |
| Pinned build | A build you choose. | revyl app branch pin, --pin on revyl build or revyl build upload, Pin “staging” to this build in a build's actions menu, or Change default build… in Build branches. |
A usable build isn't deleted and is the kind the app runs: a simulator .app
for iOS or an .apk for Android. Uploads still in progress don't count.
No upload changes a pin unless you pass --pin. A branch on Latest build runs
each new upload because it's the newest; a pinned branch keeps its pin.
On an app's Builds page, the Default tag marks each branch's default build. In branch lists, Default with a house icon marks the app's default branch.
Git branch detection
Without --branch, uploads go to your Git branch, and runs try it first. The
CLI, the terminal UI, the Revyl GitHub Actions (upload-build, run-test,
run-workflow), and MCP read the Git branch of the code they run from:
| Where it runs | Git branch |
|---|---|
| Your machine, or CI that checks out a branch | The checked-out branch of the project, or of the folder you pass with -C, including linked worktrees. |
| GitHub Actions or Buildkite, for a pull request | The pull request's source branch, such as feature-login. A source in another repository is owner:branch, such as contributor:feature-login. |
| GitHub Actions or Buildkite, other runs | The branch being built. A job without a checkout reads it from the job's environment. |
revyl build --remote | The branch of the source it submits. |
| MCP | The branch of the project in project_dir or REVYL_PROJECT_DIR, never the server's own folder. See MCP setup. |
| A tag, or a detached checkout outside GitHub Actions and Buildkite, as GitLab CI and Jenkins use by default | None, so the app's default branch is used. Pass --branch, for example --branch "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" on GitLab. |
What happens with the detected branch:
- Uploads go to the Git branch and create it if it doesn't exist. A Git
branch name that can't be a Revyl branch fails with
Detected Git branch is not a valid Revyl branch; use --branch <name>. - Runs, sessions, and explorations use the Git branch when it has a usable
build. Otherwise they use the app's default branch and say so, with a notice
such as
Git branch "feature-login" has no usable build; using the app's default branch "main".A workflow decides this for each app. If the default branch has no usable build either, the run fails and names both branches.
Build lists, Atlas, the MCP build list, and the terminal UI's build browser show All branches until you pick one.
Detection never changes saved settings, and no setting turns it off. To opt
out, pass --branch, or set the Revyl GitHub Actions' branch input.
How a run picks its build
A run uses the first of these that applies:
- A choice for this run, such as
--branch staging,--build-id,--all-branches, or a branch or build picked in a run dialog. See Run options. - The saved choice of the test, workflow, or embed, unless it's Default branch.
- Your Git branch, when it has a usable build. See Git branch detection.
- The app's default branch.
A choice has a branch part and a build part. In the dashboard, both live in one Build field.
| Branch | Uses |
|---|---|
| Default branch | No saved branch, so the run goes on to steps 3 and 4: your Git branch, then the app's default branch as it is when the run starts. |
| A named branch | That branch. Saved choices store its ID, so they survive a rename. |
| All branches | The newest usable build on any of the app's branches, always Latest build. |
| Build | Uses |
|---|---|
| Automatic | The branch's default build: its pinned build, or else its Latest build. |
| Latest build | The branch's newest usable build, even when the branch is pinned. |
| Exact build | One build, by ID. It doesn't pin the branch. |
Once a run starts, its branch and build stay fixed, including retries.
Choices you make fail strictly. If a branch or build chosen for the run, or
saved on the test, workflow, or embed, can't be used, the run fails instead of
using another one, for example
Branch "staging" on "My App" has no usable build. Upload a build to that
branch or choose another. Only a detected Git branch falls back.
New saved choices
- Tests created in the dashboard, and workflow app overrides when you choose an app, start on All branches · Latest build, the newest build on any branch, so a pull request job can run another pull request's upload. To test the pull request's own build, pass the upload's build ID to the run, or save it on Default branch so it runs on the pull request's own branch.
- Tests saved from a session, tests created with the CLI, YAML, MCP, or the API, and embeds start on Default branch with Automatic.
Pull request builds
Pull request jobs on GitHub Actions and Buildkite upload to the pull request's source branch. The Revyl GitHub and GitLab apps upload their preview builds there too, as ordinary builds that can become that branch's Latest build. The apps skip pull requests from forks. If a source branch name can't be a Revyl branch, the app's Revyl configuration check fails and no preview is built.
Every source branch you upload from becomes a Revyl branch. To choose between per-PR branches and one shared branch, see Choose where CI uploads go.
Common tasks
Keep a source-of-truth branch current
Choose the branch you want to stay current, usually main or staging, and
keep feeding it new builds.
Upload each new build to the branch
Upload with --branch and keep the build ID:
revyl build upload --file ./build/app.apk --app "$APP_ID" \
--branch main --yes --json > build.json
BUILD_ID=$(jq -er '.build.build_id' build.json)Test or explore that build
Run your tests on it, explore it to keep the branch's Atlas map current, or both:
revyl workflow run smoke-tests --android-app "$APP_ID" --android-build-id "$BUILD_ID"
revyl explore run "$APP_ID" --build-id "$BUILD_ID"Repeat for every new build
Run it however suits your team: in CI after every merge, on a daily or weekly schedule, or from a cloud AI agent automation, such as a Cursor automation. Keep main or staging current has GitHub Actions workflows to copy for after merges and weekly.
Merging a pull request doesn't move its builds or Atlas evidence to main, so
each new build needs its own run.
Make it the app's default branch. Choices saved on Default branch then use it in dashboard runs, and in runs from your code when your Git branch has no usable build:
revyl app branch default main --app "My App"Test a branch
Run once on a branch without changing saved settings:
revyl test run login --branch staging
revyl workflow run smoke --branch stagingA workflow's --branch applies to every app it runs; it fails if one of them
doesn't have that branch or has no usable build there. In the dashboard, use
Run on a branch… on a workflow or a selection of tests: type a branch name
or choose All branches, then Automatic or Latest build. On one test,
use Run with a build… (Run async with a build… in the editor).
To make every run use a branch, save it:
revyl test build set login --branch staging
revyl workflow app set smoke --ios <ios-app-id> --ios-branch stagingIn the dashboard, set the Build field in the test's settings, or turn on App Override in the workflow's Settings tab and set iOS build or Android build. For every option, see Run options and Saved test and workflow choices.
Pin a release candidate
Pin staging to one build so runs on it keep that build while newer builds
arrive:
revyl build list --app "My App" --branch staging
revyl app branch pin staging <build-id> --app "My App"Runs that choose Latest build still use the branch's newest build. Unpin with
revyl app branch unpin staging --app "My App".
Compare two branches
- Atlas. Pick a branch in Atlas's scope control, or open each map with
revyl atlas open --app "My App" --branch <name>; links keep the branch, so you can open two side by side.revyl atlas briefandrevyl atlas graphtake--branchtoo. Branches filter evidence, not screens: merging or hiding screens applies to the whole app. - Test results. Run the same workflow with
--branch mainand--branch feature-loginand compare the reports. A test's history page can be filtered by branch; workflow history shows each run's build and branch.
Check which build a run uses
revyl build which shows the branch and build a run on the app would use, and
why, without starting one:
revyl build which --app "My App"
revyl build which --app "My App" --branch staging
revyl build which --app "My App" --jsonIt uses --branch, otherwise your Git branch when it has a usable build,
otherwise the app's default branch, then that branch's default build. It prints
the app, branch, reason, build, and default build, and prints the fallback
notice on stderr when your Git branch couldn't be used.
- It reads the app's selection, not a test's or workflow's saved choice.
HEADhere is a literal branch name.- A failed selection exits nonzero without a result.
Manage branches
| Task | CLI | App settings > Build branches |
|---|---|---|
| List | revyl app branch list --app "My App" | The branch list |
| Create | revyl app branch create staging --app "My App" | New branch |
| Rename | revyl app branch rename staging release --app "My App" | Rename… |
| Set the default branch | revyl app branch default staging --app "My App" | Make default branch… |
| Pin or unpin | revyl app branch pin staging <build-id> --app "My App", revyl app branch unpin staging --app "My App" | Change default build…, then Latest build or Pinned build |
Each row in Build branches also offers View builds and Open in Atlas.
Uploads also create branches: an upload to a branch name or Git branch that doesn't exist yet, or a new name typed in the dashboard's upload sheet. Creating a branch that exists returns it. A pin must be a build on that branch. MCP tools can't rename or pin branches or set the default branch.
Branch names are case-sensitive, unique within the app, 1 to 255 characters, with no leading or trailing spaces and no control characters. A rename keeps the branch's ID, so saved choices follow it; scripts that pass the old name need the new one, and an upload from a Git branch with the old name creates a new branch. Branches can't be deleted or archived. Lists show the default branch first, then the rest by latest activity: creating a branch, a new build, a rename, or a pin change moves it up.
From the CLI:
revyl app branch list --app "My App" --page 2 --page-size 100 --jsonlistmarks the default branch(default)and shows 50 branches per page (--page-sizeup to 100). Its columns are BRANCH, BRANCH ID, DEFAULT BUILD, and BUILD ID.- DEFAULT BUILD reads
Latest build (v4),Latest build (no builds),Pinned build (v1.2), orPinned build (v1.2, unavailable). BUILD ID is the build a run on the branch uses now, or-when there is none. pintakes the ID of a build on that branch, not a version label. List IDs withrevyl build list --app "My App" --branch staging.- The other commands print the branch as
Branch: <name> (<id>), or JSON with--json.
CLI reference
The branch and build options, output, and JSON fields of each CLI command.
Upload and list builds
revyl build --profile preview --platform ios --branch staging
revyl build upload --file ./app.apk --app "My App" --branch staging --pin
revyl build list --app "My App"
revyl build list --app "My App" --branch staging --page 2 --page-size 50
revyl build list --app "My App" --branch HEAD--branchchooses the upload's branch and creates it if it doesn't exist. Without it, the upload goes to your Git branch, created if needed, or the app's default branch when none is detected. When--repoand--commitname a different repository or commit than the checkout, the checkout doesn't choose the branch. Pass--branchwhen the artifact wasn't built from this checkout.revyl initchooses its uploads' branch the same way, so the first build it uploads to an app it created sets that app's default branch.--pinpins the branch to the new build. If the version label already exists, the upload reuses that build and doesn't pin; the warning prints therevyl app branch pincommand to run.- Version labels are unique within an app among builds that aren't deleted. A
label used on another branch fails with
This version already belongs to another branch; use a unique version. - The output prints
App:,Build Version:,Build ID:, andBranch:with what a run on the branch uses now:staging (Latest build: runs use this upload),staging (Latest build: runs use v4),staging (pinned to v1.2; runs that don't choose a build keep using v1.2), orstaging (pinned to this build). Keep the build ID to run that exact build later; a remote build job ID isn't a build ID.
build list shows every branch of the app. --branch <name> narrows it to one
branch. When no Revyl branch has that name, it lists builds built from a Git
branch of that name instead. --branch HEAD means your checked-out Git branch
and fails outside Git or on a detached checkout, as in pull request CI.
The heading says which filter matched: Builds (Revyl branch: main): or
Builds (Git branch: main):. Tables show GIT BRANCH, the branch the build
was built from, and across branches REVYL BRANCH, the dashboard's
Branch column.
Run options
These apply to one run and don't change saved settings:
revyl test run login --branch staging
revyl test run login --branch staging --build-selection latest
revyl test run login --build-id <build-id>
revyl test run login --all-branches
revyl workflow run smoke --branch staging
revyl workflow run smoke --ios-app <app-id> --ios-build-id <build-id>
revyl explore run "My App" --branch staging
revyl device start --app-id <app-id> --branch staging
revyl device install --app-id <app-id> --all-branches| Option | Uses |
|---|---|
--branch <name> | That branch. Fails if it doesn't exist or has no usable build. |
--build-selection inherit | Automatic, the default: the branch's default build. |
--build-selection latest | Latest build: the branch's newest usable build, even when it's pinned. |
--all-branches | All branches · Latest build: the newest usable build on any of the app's branches. It can't be combined with --branch, an exact build, or another --build-selection. |
--build-id <id> | One exact build, on test run, explore run, test create, and test build set. |
--build-version-id <id> | One exact build, on device start, device install, and revyl dev. |
--ios-build-id, --android-build-id | One exact build per platform on workflow run, with --ios-app or --android-app. --ios-build and --android-build take a version label instead. |
- A workflow's
--branchand--build-selectionapply to every app it runs. Each app needs that branch and, unless you give it an exact build, a usable build there; otherwise the workflow doesn't start and the error names the apps. Without them, each app falls back from your Git branch on its own. - With test or workflow
--build, the new build uploads to--branch, or your Git branch, and the run uses that exact build. Don't combine--buildwith--build-selection. --ios-appand--android-appreplace the workflow's saved app for one run; platforms you don't override use each test's saved choice.- Output names the branch of each build, such as
Build: v1.4 from branch feature-login. revyl device history --app-id <id> --branch staginglists sessions that started with a build from that branch.revyl devkeeps one branch for startup and every rebuild, and doesn't take--all-branches. See Dev Loop for how it picks the branch and build.
Saved test and workflow choices
revyl test build show login
revyl test build set login --branch staging --build-selection latest
revyl test build set login --build-id <build-id>
revyl test build set login --all-branches
revyl test build set login --build-selection inherit
revyl test build clear login
revyl test create login --from-file ./login.yaml --branch staging
revyl workflow app set smoke --ios <app-id> --ios-branch staging --ios-build-selection inherit
revyl workflow app set smoke --ios-automatic
revyl workflow app set smoke --ios-all-branches
revyl workflow app show smoke
revyl workflow app disable smoke
revyl workflow app enable smoke
revyl workflow app clear smoketest build showprints the saved Branch (Default branch, All branches, orstaging (<branch-id>)) and Build (Automatic, Latest build, orExact build v1.4 (<build-id>));--jsongives the saved IDs.test build setsaves a branch and build.--build-selectionalone keeps the saved branch, so on a test saved on All branches it saves Default branch. To move a test saved on a named branch back to Default branch, choose Default branch in its dashboard settings, or push YAML withbuild_selection: inheritand nobranch_id.test build clearresets a test to All branches · Latest build.test createtakes the same--branch,--build-selection,--build-id, and--all-branches, also with--from-fileor--from-session. Without them or a choice in the YAML, a new test starts on Default branch.- Workflows save iOS and Android separately with the
--ios-*and--android-*flags.--ios-automaticsaves Default branch with Automatic for iOS, and--ios-all-branchessaves All branches · Latest build. workflow app shownames each app and branch with its ID.disableturns the app override off and keeps its choices,enableturns it back on, andclearremoves the choices, like turning the override off in the dashboard.
Terminal UI
- The app build browser opens on All branches, including when you return to it. Its branch filter narrows the list and never chooses an upload's destination or a run's build.
- Run, session, and upload pickers start with your Git branch when one is
detected:
Git branch (feature-login) · falls back to basefor runs and sessions, orGit branch (feature-login)for uploads, which create it. Pickers that save a choice, and pickers with no Git branch, start withDefault branch (base)and its default build. The other branches follow, most recently active first, with the app's default marked(default). - Pickers for runs, sessions, and saved choices list All branches · Latest build after the first row. Upload pickers and the build browser don't.
- After a branch, run, session, and saved pickers offer Automatic, Latest build, or Exact build. A workflow run takes a branch name and offers Automatic or Latest build; with no name, it offers Keep saved settings or All branches · Latest build.
- Lists load one page at a time:
mloads more,/searches,Rretries a failed page, andnnames a new branch in an upload picker.
JSON fields
- Uploads and builds:
branch_idandbranch_name, with the build ID at.build.build_idforbuild upload --fileand.version_idforbuild upload --url. - Runs (
test run,workflow run,workflow status,workflow report,explore run,device start,device install):branch_namefor each build,branch_sourcefor where that branch came from, andgit_branch_name:branch_sourceisgit_branch(your Git branch),explicit_branch(--branch, or the branch saved on the test or workflow), ordefault_branch(the app's default branch). It's left out when an exact build or All branches · Latest build chose the build.git_branch_nameis the Git branch the run sent, present only when the run consulted it: withgit_branch, and withdefault_branchwhen your Git branch couldn't be used (it doesn't exist, has no usable build, or isn't a valid Revyl branch name). It's left out forexplicit_branch, an exact build, All branches · Latest build, and a run with no Git branch. For example, a fallback reports{"branch_name": "base", "branch_source": "default_branch", "git_branch_name": "feature-login"}.
build which:app_id,app_name,branch_id,branch_name,branch_source(explicit_branch,git_branch, ordefault_branch, which a fallback also reports),git_branch_name(as for runs: present withgit_branch, and withdefault_branchafter a fallback),build_id,build_version, andbranch_build_selection(latestorpinned: the branch's default build).build list:versions,count,total,page,page_size, andhas_next; with--branch, alsobranch_filterandbranch_filter_kind(revyl_branchorgit_branch).app branch list:branches, each withis_default,pinned_build_id,pinned_build_version,pinned_build_available, andlatest_build(absent when the branch has no builds), plustotal,page,page_size, andtotal_pages.
Deprecated flags
--set-current on revyl build and revyl build upload pins like --pin.
--set-current=false and --no-set-current do nothing. Each prints a warning
on stderr, --json output is unchanged, and none appears in --help.
Related
- CI Builds: where CI uploads go
- Dev Loop: how
revyl devpicks and keeps its branch - MCP tool reference: branch and build inputs
- YAML test format: a test's saved build fields
- Atlas from the CLI: branch scope for reads