Skip to main content
Every test run belongs to a concurrency group. Runs in the same group (within a project) execute one at a time. When a run is triggered and its group already has an active run, the new run’s on_conflict policy decides what happens. Both are set on the trigger request, so each caller says what it needs: a CI pipeline can cancel the previous run for the same branch, a nightly schedule can wait its turn, and a one-off run from the dashboard can opt out.

The concurrency object

Omit concurrency to use the project’s default (see Legacy project setting). Send "concurrency": null for no limit at all.

on_conflict

The policy belongs to the new run. A wait run never cancels a running one; a cancel run always does, even if the running one was itself triggered with wait. Only runs created in the last 6 hours count as active, so a stuck run cannot block a group forever.

Choosing a group

Groups are plain strings compared exactly. Include whatever makes two runs “the same thing” for you, and nothing else.

Scheduled runs

A schedule carries its own block in ENVIRONMENTS.yaml:
A plain scheduled_trigger: "0 2 * * *" still works and uses the project default. Configure scheduled concurrency in ENVIRONMENTS.yaml alongside the cron expression. The change takes effect only after its pull request is merged into the test repository’s default branch.

What a run records

Every run stores the group and policy it was created with, so the decision stays accurate even if settings change later. It comes back as concurrency on the run in the API and shows under Concurrency in the run’s details on the dashboard, where the group links to the list of all runs in that group. GET /api/test-runs?concurrency_group=<group> filters the list.
concurrency is null when no limit applied. Reruns reuse the source run’s group and policy.

Legacy project setting

Projects created before per-run concurrency have a project-level setting (test_run_concurrency_limit and test_run_concurrency_behavior) that still applies to any trigger that omits concurrency. It is frozen: it cannot be changed any more (the API rejects writes and the dashboard no longer shows it), and it will be removed once callers set their own blocks. To change how a project’s runs are limited, set concurrency on the trigger request or scheduled_concurrency on the environment. The frozen scope maps to a group as follows: /<repo> is present when the run was triggered with a build that names a repository. For branch_environment, a run without build.branch has no group and is not limited. The legacy behavior independently maps to the conflict policy: