Release gates
Release gates are the refusals a release, a build dispatch, a rollback or a release cut can return. Reach for this page when a request came back with a code and you need its meaning and its fix.
How it works
Every gate runs before anything is dispatched, metered or written. A refusal spends nothing and moves nothing: the channel stays where it was.
The refusals group by what they check. Request gates read the body. Channel, environment and build index gates read the project's records. Workflow gates read the mirror. Rollback and cut gates belong to those two endpoints alone. The operator gate and the quota gates read the workspace.
Access refusals are decided at the door, before any gate on this page runs. They apply to every endpoint here and sit in the last table.
Not every refusal carries a code. The refusals with no code come back with a message alone, so a client that switches on code must handle that shape too.
Reference
Request gates
| Code | Status | Meaning | Fix |
|---|---|---|---|
INVALID_BODY | 400 | The body does not match the endpoint's schema. details names the failed fields. GET /api/projects/{id}/releases/promotion-runs answers the same code without ids, with the message Missing dispatch ids to correlate. | Send the fields the endpoint documents. For a release, buildId is the only required field. See the API reference |
INVALID_ORIGIN | 400 | The dispatch body named an origin the platform does not know. The accepted values are mcp, code, console, cli, cli-unknown, import and api | Send one of those seven, or leave origin out. A dispatch with no origin is recorded as console |
Channel gates
| Code | Status | Meaning | Fix |
|---|---|---|---|
UNKNOWN_CHANNEL | 404 | The release names a channel this project does not declare. The message lists the declared channels, which always include stable and preview. A release cut answers the same code when it names an undeclared channel | Create the channel under Project settings, Release channels, or release to a channel in the list. See Channels |
UNKNOWN_SOURCE_CHANNEL | 404 | The release names a sourceChannel this project does not declare. The canary alias older projects once accepted is gone, so canary answers this code too | Release from a channel in the list, or leave sourceChannel unset. A release to preview sources from preview; any other channel sources from itself |
CHANNEL_STATE_INDETERMINATE | 409 | The channel list could not be read at decision time, so the gate cannot tell whether the channel exists. A cut answers the same code | Retry |
A channel takes a release one second after it is created, because this gate reads the same channel record the console writes.
Environment gates
A push to the Release branch is made for Stable. Every other push and every pull request build are made for Preview. preview takes any build. stable and every channel you create take a build made for Stable, and a store submission takes the same. The build record stores the environment as buildEnv, production or staging.
| Code | Status | Meaning | Fix |
|---|---|---|---|
BUILD_ENV_MISMATCH | 409 | The build was made for Preview and the target takes Stable builds. A release to stable or to any channel you created answers it, and so does a store submission | The message carries the fix: "Rebuild this commit for Stable (Builds, Rebuild), then release it." The release form and the submission page offer Rebuild for Stable · 1 build. in place, or release to preview |
UNSTAMPED_BUILD_BLOCKED | 409 | The build predates environment stamps and the project's Stable environment is populated, so the gate cannot place it. A release to stable and a store submission both answer it | Rebuild the commit for Stable, then release or submit that build. New builds are stamped |
ENV_STATE_INDETERMINATE | 409 | The environment state could not be read at decision time, on a release or a store submission of an unstamped build | Retry. If it persists, check Environments |
Build index gates
| Code | Status | Meaning | Fix |
|---|---|---|---|
UNKNOWN_BUILD | 404 | The sha is not in the build index. The platform never produced that build | Build the commit first, or correct the buildId |
BUILD_ENV_SERVED | 409 | The commit is the current build of stable or of a channel you created, and the rebuild names Preview. A build record is keyed by commit, so the rebuild would change the environment recorded for the build that channel points at, and every gate above would then read the wrong environment for a served release | Rebuild another commit for Preview, or release a new build to the channel first. force: true does not pass it, for a person or a token |
ENV_STATE_INDETERMINATE | 409 | The channel list could not be read at decision time and the rebuild names Preview, so the platform cannot tell whether a served channel stands on this commit | Retry. A Stable rebuild is not held by this |
REBUILD_REPLACES_LIVE_ARTIFACT | 409 | The rebuild would overwrite artifacts a live channel serves, for an environment other than the one that channel requires, on a project whose channel bindings differ from the default | Rebuild with the environment the channel requires. A person in the console can overwrite anyway with Replace and rebuild, which sends force: true; a project token is refused 403 REBUILD_FORCE_NEEDS_A_PERSON for it, so its message never offers it. A Stable rebuild of a commit that only preview serves passes without force |
Workflow gates
| Code | Status | Meaning | Fix |
|---|---|---|---|
RELEASE_WORKFLOW_NOT_FOUND | 404 | A release, a rollback and GET /api/projects/{id}/releases/promotion-runs read the project's release workflow, and the mirror carries none | Reconnect the project under Project settings, GitHub integration, which restores the mirror's workflows |
RELEASE_DISPATCH_FAILED | 500 | One release run dispatches per browser, and every one failed | Retry. A release where at least one browser dispatched answers 200 and names the browsers that did not |
Rollback gates
| Code | Status | Meaning | Fix |
|---|---|---|---|
NO_ROLLBACK_TARGET | 409 | The channel's latest release left no earlier build: a first release, a release of the build the channel already held, or a channel with no event log | Release a build by id with POST /api/projects/{id}/releases. The channel gains a target once a second, different build enters it |
ROLLBACK_HISTORY_UNREADABLE | 503 | The release history could not be read. A timeout, a registry error, a rejected token and a non-JSON answer all read this way | Retry. If it persists, the registry is the thing to check |
ROLLBACK_DISPATCH_FAILED | 502 | A target resolved and every browser's dispatch failed | Retry. A run where some browsers dispatched answers 200 and names the ones that did not |
Release cut gates
A cut bumps the manifest version in your source repository and opens a release PR, so its refusals are about that repository.
| Code | Status | Meaning | Fix |
|---|---|---|---|
NO_SOURCE_REPO | 409 | The project is not connected to a source repository | Connect one under Project settings, GitHub integration |
APP_NOT_INSTALLED | 409 | The extension.dev GitHub App is not installed on the source repository | Reconnect the project, which reinstalls the App |
MANIFEST_NOT_FOUND | 409 | No manifest.json with a version was found. The cut looks in the project's root directory first, then in the usual locations | Bump the version by hand and push, or set the root directory to the folder that holds the manifest |
VERSION_INVALID | 400 | The requested version is not one to four dot separated integers from 0 to 65535, such as 0.3.0 or 1.2.3.4 | Send a version in that shape, or send a level of patch, minor or major instead |
VERSION_NOT_AHEAD | 400 | The requested version is not greater than the one the manifest carries. The message names the current one | Ask for a higher version |
VERSION_UNPARSEABLE | 409 | The manifest's current version could not be read as a version | Name the version you want with version, or correct the manifest and push |
SOURCE_REPO_UNREACHABLE | 409 | The source repository answered 404 through the GitHub App. Renamed, deleted and uncovered by the installation all read this way | Reconnect the project under Project settings, GitHub integration |
Operator gate
| Code | Status | Meaning | Fix |
|---|---|---|---|
BUILD_DISPATCH_DISABLED | 429 | An operator paused build dispatch. Every request that dispatches a run on your mirror answers this code while the pause holds | None on the client. Wait for the operator to lift the pause |
The pause holds the console Rebuild button, POST /builds/dispatch, a release, a rollback, a release cut, a store submission from the console, the API or the CLI, a rollout raise, a submission cancel and a store credential recheck.
The lanes with nobody waiting hold too. The push webhook answers its delivery with the code DISPATCH_DISABLED and skips the build. The dispatch sweep records a kill_switch refusal. The store-status sweep returns paused. A merged release PR stays pending and builds after the pause lifts. The first build after an import is skipped with a line in the import stream, and the project builds from the Builds page once dispatch resumes.
Refusals with no code
| Status and message | Endpoint and cause |
|---|---|
400 No commits found on the target branch. | Dispatch. The commitSha was not found, or the branch carries no commits |
400 GitHub App installation not found for this project. | Dispatch. No App installation is recorded. Reconnect the project |
404 No build workflow found for this project. | Dispatch. The mirror carries no build workflow. Reconnect the project |
405 Method not allowed, with an Allow header | Any endpoint here, reached with the wrong method |
500 Internal server error | Any endpoint here, when the handler threw. Retry, and read the run history before releasing again |
Quota reasons
Every quota refusal names one of twelve reasons. Where the answer lands depends on the endpoint, so read this table with the door codes below it.
| Reason | Meaning | Fix |
|---|---|---|
monthly_cap | The monthly build allowance is spent | Wait for the monthly reset, or raise the plan at extension.dev/pricing |
burst_cap | Too many push builds inside a sliding 60 minute window: 12 on free, 30 on pro. Decided on the push webhook and recorded, not answered | Wait. The window slides and capacity returns as older dispatches age out |
kill_switch | An operator paused dispatch. This is the reason behind BUILD_DISPATCH_DISABLED | None on the client |
push_builds_exhausted | The workspace spent its push builds for the month: 25 on free, 350 on pro. Only a git push on a branch the project builds is counted | Click Rebuild in the console to run the same commit now. The count resets next month. See CI and releases |
counter_unavailable | The usage counter could not be read, so the gate refuses rather than dispatches unmetered | Retry |
entitlement_unavailable | The workspace's plan could not be read, so no ceiling could be established | Retry. This is never a statement about your allowance |
unattributed | The push could not be tied to a workspace, so nobody could be billed. Decided on the push webhook and recorded, never answered | None on the client. The fault is in the platform's record of the project |
fork_pull_request | The commit sits on a pull request whose head branch lives in another repository. A build runs that code and spends your allowance, so a fork PR does not start one | Read the change, then click Rebuild in the console to run that commit yourself, or merge the PR, which builds it as a push |
macos_runner_not_included | The plan carries no Safari lane. Safari builds and App Store submissions run on a macOS runner and belong to the paid plan. A free workspace is refused before any dispatch. A build that names Safari with other browsers drops the Safari leg and builds the rest; Safari alone is refused | Raise the plan at extension.dev/pricing. Chrome, Edge and Firefox are never touched by this |
macos_notarization_not_included | A macOS launcher must be signed and notarized before macOS opens it. That signing belongs to the paid plan, and a free workspace is refused before anything is signed or submitted to Apple. The release itself succeeds | Raise the plan and release again. Signing with your own Apple Developer account stays free |
safari_submissions_exhausted | The 15 App Store submissions a paid workspace carries in a month are spent. A submission also spends one from the monthly build allowance | Wait for the month to roll, or submit to the other three stores |
testing_workspace | The workspace is marked as a testing workspace and is read-only: builds, store submissions, releases, shares and AI generation are refused. Only an operator sets or clears the marker | None on the client. No retry or reset clears it |
Quota door codes
An endpoint with a person waiting answers at the door. The code comes in two dialects.
| Endpoint | Spent allowance | Testing workspace | Unreadable counter |
|---|---|---|---|
POST /builds/dispatch and the console Rebuild button | 429 with the reason as the code, such as monthly_cap | 403 testing_workspace | 503 counter_unavailable |
| Release, rollback, cut and store submission | 429 BUILD_QUOTA_EXHAUSTED | 403 TESTING_WORKSPACE_READ_ONLY | 503 BUILD_COUNTER_UNAVAILABLE |
| Safari build or App Store submission | 403 macos_runner_not_included or 403 safari_submissions_exhausted | 503 SAFARI_ALLOWANCE_UNAVAILABLE |
The push webhook answers nobody, so push_builds_exhausted, fork_pull_request, burst_cap and unattributed are recorded against the workspace and read back in the console under Usage and limits.
macos_notarization_not_included is decided while a release is being made, after the release succeeded. It never sets the response status. It is reported inside the release response under notarization.refused, with the code MACOS_NOTARIZATION_NOT_INCLUDED, the browsers it applies to and an upgrade link. Every extension package is published, and the Linux and Windows executables are built and downloadable.
Access gates
| Code | Status | Meaning |
|---|---|---|
UNAUTHENTICATED | 401 | No session and no token |
BAD_TOKEN | 401 | The token does not verify, or carries no project |
INVALID_PROJECT_ID | 400 | The {id} in the path is not a project id |
TOKEN_PROJECT_MISMATCH | 403 | The token names a different project |
OWNER_REQUIRED | 403 | The action is owner only, and the caller is not one |
A cut, a rollback and GET /api/projects/{id}/releases/promotion-runs are owner only. A request that carries a token is judged on the token and never falls back to the session. See the API reference.
When it fails
- A code is not on this page. The refusal came from the build itself, not from a gate. Open the build's logs with
GET /api/projects/{id}/builds/{buildId}/logs. - A
429with no reason you can place. Read thecode. The dispatch endpoint answers the reason itself, while a release, a rollback, a cut and a submission answerBUILD_QUOTA_EXHAUSTED. Both mean the same spent month. - A release answered
200and a browser is missing. ReadfailedBrowsersin the body. Only a release where every dispatch failed answersRELEASE_DISPATCH_FAILED. - A release answered
200and no macOS launcher shipped. Readnotarization.refusedin the body. The plan carries no signing, and the rest of the release went out.