Gate
Org Policies & Freezes
Set one gate policy for every repository, lock the keys repositories may only tighten, and freeze releases on a schedule or on demand.
Each repository setting its own rules leaves gaps an auditor will find; org policies set one floor for the whole workspace, for platform and security teams.
Beta, early access. A workspace without access gets
403 FEATURE_EARLY_ACCESSand a link to request it.
An org policy is one gate policy for your whole workspace. The Release Gate merges it with each repository's bumpkit.yml. You can lock keys so a repository can only make them stricter. Change freezes and release windows stop releases at set times, and the version consistency check catches manifests and tags that disagree with the version being released.
This page covers four features. Three of them are behind their own flag:
| Feature | Flag |
|---|---|
| Org-wide policies | org_policies |
| Release windows and change freezes | change_freezes |
| Version consistency | version_consistency |
| Replay-safe checks | none |
Org-wide policies
Beta, early access. A workspace without access gets
403 FEATURE_EARLY_ACCESSand a link to request it. Flag:org_policies.
An org policy is written exactly like a bumpkit.yml, with one extra top-level key: locked, a list of gate keys that repositories may only tighten.
locked:
- block_on
- require_checks
- on_error
gate:
block_on: [secret, data_loss, breaking]
require_checks: [ci/test]
on_error: fail
security_alerts: highYou can lock any of these keys:
block_on, require_approval, on_error, require_checks, require_check_apps, security_alerts, vulnerabilities, break_glass, hotfix, blast_radius, deploy, cross_repo, timezone, windows, outside_windows, freezes, quality, exports, behavior, fix_tasks, stacks
An unknown key in locked is rejected when you save the policy. For the meaning of each key, see the bumpkit.yml Reference.
Where the org policy lives
You store the org policy in one of two ways:
- In Bumpkit. You upload the policy text. Every change is saved as a new numbered version with a message, a diff and an audit record. The policy must be under 64 KB.
- In a config repository. You point Bumpkit at a repository, such as
acme/.github. The gate reads that repository'sbumpkit.ymlfrom its default branch every time it runs. The Bumpkit GitHub App must be installed on it, and the installation must belong to the same workspace.
If Bumpkit can't read or parse the org policy, the gate fails closed. The API returns 503 with code ORG_POLICY_UNAVAILABLE, and the GitHub check run fails with "Couldn't read the org policy".
When the org_policies flag is off, the gate ignores any stored org policy and uses only the repository's bumpkit.yml.
How the org policy and bumpkit.yml combine
For each key:
- If the repository's
bumpkit.ymldoesn't set the key, the org policy's value applies. - If the key isn't locked, the repository's value replaces the org value.
- If the key is locked, the stricter of the two applies. A value that would loosen the policy is ignored, and the gate decision says why with a reason named
org_policy: <key> is locked.
How "stricter" works for each locked key:
| Key | Result when locked |
|---|---|
block_on |
Union of both lists. The repository can add kinds, not remove them. |
require_checks |
Union of both lists. |
require_check_apps |
The org's issuers always apply. The repository can add checks with their own Apps, but can't drop a check or point it at another App; the decision says "bumpkit.yml can't remove or replace trusted check issuers; the org policy keeps them." |
require_approval |
Org rules plus any extra rules from the repository. |
on_error |
If the org sets fail, it stays fail. |
security_alerts, vulnerabilities |
The repository can lower the severity threshold, not raise it. |
break_glass |
If the org disallows overrides, the repository can't allow them. |
hotfix |
The stricter of each setting: union of block_on, the smaller max_files, the shorter review_within, the stricter backports. |
blast_radius |
Union of approve_areas. The org's max_depth always applies; a different value in bumpkit.yml is ignored with the note "bumpkit.yml can't loosen blast_radius.max_depth; the org policy's 2 applies." |
timezone |
Must match the org's. A different value is ignored. |
windows |
The repository can't change the org's release windows. Add freezes to narrow them. If the org sets no windows, the repository's apply. |
outside_windows |
If the org sets block, it stays block. |
freezes |
Org freezes plus any extra freezes from the repository. |
fix_tasks |
The repository can only tighten it: off < approve < block. Unset counts as block. |
deploy, cross_repo, quality, exports, behavior |
Must match the org's exactly. A different value is ignored. |
See the effective policy
Run this in a repository to see the policy the gate applies there and where each value came from:
bumpkit policy show --effectiveOrg policy v4, merged with this repository's bumpkit.yml
KEY VALUE FROM
block_on - secret - data_loss - breaking - version_mismatch org+repo (locked)
require_checks - ci/test org (locked)
on_error fail org (locked)
security_alerts high repo
! bumpkit.yml can't remove breaking from block_on; the org policy keeps them.The FROM column is one of default, org, repo, org+repo or not set. The command reads bumpkit.yml as it is at --base (default HEAD), the same way the gate reads it at a pull request's base. Use --file PATH to test an unsaved file, and --repository owner/name to override the repository detected from the origin remote.
The same merge is available from the API:
curl -X POST https://bumpkit.dev/v1/policy/effective \
-H "x-api-key: $BUMPKIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"repository": "acme/api", "policy": "gate:\n on_error: pass\n"}'{
"org_version": 4,
"policy": "gate:\n on_error: fail\n",
"sources": [
{"key": "on_error", "value": "fail", "from": "org", "locked": true, "note": "bumpkit.yml can't loosen on_error; the org policy's fail applies."}
]
}org_repository is also set when the org policy is read from a config repository.
Change the org policy
Only workspace owners and admins can change the org policy. Anyone in the workspace can read it.
bumpkit policy set org-policy.yml --message "Require CI everywhere"
bumpkit policy set --repository acme/.github
bumpkit policy set --clearbumpkit policy set validates the file, shows the diff against the current version and asks you to confirm. Pass -y to skip the prompt. Pass exactly one of a file, --content YAML, --repository or --clear. Clearing saves a new, empty version, so the history is kept.
To review changes:
bumpkit policy show
bumpkit policy history --limit 50
bumpkit policy diff v3 v4bumpkit policy diff FROM [TO] compares two versions. TO defaults to the latest version.
You can also edit the org policy on the Policy page of the dashboard, under the Policy and freezes tab.
Every gate decision made under an org policy records the version it used. The POST /v1/gate response includes it as org_policy_version.
Release windows and change freezes
Beta, early access. A workspace without access gets
403 FEATURE_EARLY_ACCESSand a link to request it. Flag:change_freezes.
Release windows say when releases may ship. Change freezes say when they may not. You declare both under gate: in bumpkit.yml or in the org policy, and you can declare a freeze on demand from the CLI, the API or the dashboard.
Release windows
gate:
timezone: America/New_York
windows:
- days: [mon-thu]
from: "09:00"
to: "17:00"
outside_windows: approve| Key | Description |
|---|---|
timezone |
IANA timezone name for windows and freezes that don't set their own. Defaults to UTC. |
windows[].days |
Days such as mon, tue, or a range such as mon-fri. Full names like monday also work. Omit for every day. |
windows[].from, windows[].to |
Times like 09:00. from defaults to 00:00, to to 24:00. |
windows[].timezone |
Overrides timezone for this window. |
outside_windows |
approve (the default) asks for an approval outside the windows. block fails the gate. |
Hotfixes may always ship outside the windows. See Hotfixes & Break Glass.
Scheduled freezes
Each freeze needs a name and one of three shapes: a date range, a list of dates, or a weekly schedule.
gate:
timezone: America/New_York
freezes:
- name: Holiday freeze
start: 2026-12-20
end: 2027-01-02
- name: Black Friday
dates: [2026-11-27, 2026-11-30]
mode: approve
- name: Friday afternoons
days: [fri]
from: "15:00"
hotfix: allow| Key | Description |
|---|---|
name |
Required. Shown on every gated pull request. |
start, end |
A date range, both required. Dates like 2026-12-24 or times like 2026-12-24T18:00. A date-only end includes that whole day. |
dates |
Individual days that are frozen all day. |
days, from, to |
A weekly freeze, written like a window. |
timezone |
Overrides the policy's timezone. |
mode |
block (the default) fails the gate. approve asks for an approval. |
hotfix |
What hotfixes get: allow, approve (the default) or block. |
For the full schema, see the bumpkit.yml Reference.
What the gate does during a freeze
| Situation | Outcome |
|---|---|
Freeze with mode: block |
Fails. Only a break-glass override lets the commit through. |
Freeze with mode: approve |
Needs an approval. Passes once approved. |
Hotfix, freeze hotfix: allow |
Passes. |
Hotfix, freeze hotfix: approve |
Needs an approval. |
Hotfix, freeze hotfix: block |
Fails. Only a break-glass override lets it through. |
Outside every window, outside_windows: approve |
Needs an approval. |
Outside every window, outside_windows: block |
Fails. |
| Hotfix outside every window | Passes. |
A break-glass override still works during a freeze when the repository allows overrides.
Declare a freeze on demand
Declare a freeze during an incident or over the holidays:
bumpkit freeze start --reason "Incident 42: payments degraded" --for 4h
bumpkit freeze start --reason "Year-end holiday freeze" --starts 2026-12-20T00:00:00Z --until 2027-01-04T00:00:00Z --mode approve
bumpkit freeze start --reason "Database migration window" --repository acme/api --starts 2027-01-12T06:00:00Z --for 2h| Flag | Default | Description |
|---|---|---|
--reason |
required | Why releases are frozen, 10 to 500 characters. Shown on gated pull requests and kept in the audit log. |
--repository |
every repository | owner/name to freeze. |
--mode |
block |
block, or approve to ask for an approval instead. |
--hotfix |
approve |
What hotfixes get: allow, approve or block. |
--starts |
now | When it starts, RFC 3339. |
--until |
until ended | When it ends, RFC 3339. |
--for |
none | How long it lasts, such as 4h. Use --until or --for, not both. |
List and end freezes:
bumpkit freeze list
bumpkit freeze end 3f2b9c1e-7a4d-4e0b-9a51-2d6c8e1f0a77bumpkit freeze list shows each freeze as active, upcoming or over. Owners, admins and policy admins can declare one. Ending it early takes one of them signed in to the dashboard, so an API key, the CLI or an agent can't lift a freeze. Anyone else gets 403 with "Only owners, admins and policy admins can declare or end a change freeze." Declaring and ending freezes are both audited, and so are refused attempts (freeze.declare.denied, freeze.end.denied).
You can also declare and end freezes on the Policy page of the dashboard, under the Policy and freezes tab. The page shows those controls only to the roles that can use them.
Version consistency
Beta, early access. A workspace without access gets
403 FEATURE_EARLY_ACCESSand a link to request it. Flag:version_consistency.
When the flag is on, every check compares the version Bumpkit decides for the release with the versions declared in your repository and its tags. Each mismatch is a version_mismatch finding with the file and line.
Bumpkit reads these files at the root of the repository:
| File | What it checks |
|---|---|
package.json |
The top-level version. |
pyproject.toml |
version under [project] or [tool.poetry]. |
Cargo.toml |
version under [package]. |
VERSION |
The first non-empty line. |
go.mod |
The module path has the /vN suffix that major version 2 and up need, and no suffix for v0 and v1. |
CHANGELOG.md |
The newest #, ## or ### version heading. |
A manifest may still carry the previous version until the release bumps it. Bumpkit reports:
- A manifest that names neither the release's version nor the previous one.
- A manifest left behind when another one was already bumped, so the published packages would disagree.
- A tag that already exists for this version, or a newer tag than this release.
- A
go.modmodule path with the wrong major version suffix. - A
CHANGELOG.mdwhose newest heading is neither this release nor the previous one.
The CLI sends the manifests and tags from your local checkout. For a check against a GitHub repository, Bumpkit reads them from GitHub at the head commit.
version_mismatch findings are high severity, so they show on every check. To fail the gate on them, add the kind to block_on:
gate:
block_on: [secret, data_loss, version_mismatch]See block_on.
Replay-safe checks
Beta. Replay-safe requests are on for every workspace, with no flag to enable.
POST /v1/checks and POST /v1/gate are replay-safe. If a CI job retries after a timeout, or the same request arrives twice, you get the first decision back instead of a second, possibly different, one.
A request is replayed in two cases:
- Pinned requests. A request that sends a
diff, or sendsbaseandheadas full 40-character commit SHAs, is identified by its content. Sending the same request again returns the stored result, as long as the workspace's enabled features are unchanged. For the gate, the approvals, the org policy, and any active freezes must also be unchanged. No header is needed. - Idempotency keys. Any request with an
Idempotency-Keyheader is identified by that key. The key is 1 to 200 letters, digits, dots, colons, dashes or underscores.
curl -X POST https://bumpkit.dev/v1/gate \
-H "x-api-key: $BUMPKIT_API_KEY" \
-H "Idempotency-Key: ci-run-8812-attempt" \
-H "Content-Type: application/json" \
-d @gate-request.json| Situation | Response |
|---|---|
| Same request again | 200 with the stored result and "replayed": true. |
| Same key, different request body | 422 with code IDEMPOTENCY_KEY_REUSED. |
| Same request still running | Waits for it, up to 2 minutes, then 409 with code REQUEST_IN_PROGRESS. |
| Invalid key | 400 with code INVALID_IDEMPOTENCY_KEY. |
Replayed responses don't count toward your rate limit. Bumpkit keeps stored results for one day.
The CLI sends bumpkit check and bumpkit gate up to three times in all, retrying on 502, 503, 504 and REQUEST_IN_PROGRESS, with the same key on every attempt. It generates a key for each run. To reuse a key across CI job retries, set it yourself:
export BUMPKIT_IDEMPOTENCY_KEY="$GITHUB_RUN_ID-$GITHUB_JOB"
bumpkit gateAPI endpoints
All endpoints take the x-api-key header. While a feature is in early access and your workspace isn't on it yet, its endpoints return 403 with code FEATURE_EARLY_ACCESS; when the feature is off, they return 404.
| Method | Path | Who | Description |
|---|---|---|---|
GET |
/v1/policy/org |
any role | The current org policy. 404 if there is none. |
PUT |
/v1/policy/org |
owners, admins | Save a new version. Send one of content, repository or clear, plus an optional message. Returns the new version and its diff. |
GET |
/v1/policy/org/versions |
any role | Version history. ?limit= from 1 to 200, default 50. |
GET |
/v1/policy/org/versions/{version} |
any role | One version. |
POST |
/v1/policy/effective |
any key | Merge the org policy with a bumpkit.yml. Send repository and policy. |
GET |
/v1/freezes |
any role | Current, upcoming and recent declared freezes. |
POST |
/v1/freezes |
owners, admins, policy admins | Declare a freeze. Send reason, and optionally repository, mode, hotfix, starts_at and ends_at (RFC 3339). Returns 201. |
POST |
/v1/freezes/{id}/end |
owners, admins, policy admins | End a freeze early. |
A freeze object looks like this:
{
"id": "3f2b9c1e-7a4d-4e0b-9a51-2d6c8e1f0a77",
"repository": "acme/api",
"reason": "Incident 42: payments degraded",
"mode": "block",
"hotfix": "approve",
"starts_at": "2026-10-22T14:00:00Z",
"ends_at": "2026-10-22T18:00:00Z",
"actor_email": "oncall@acme.dev",
"active": true,
"created_at": "2026-10-22T14:00:00Z"
}repository is omitted for a workspace-wide freeze. ended_at and ended_by are set once a freeze is ended early.
Agents use the MCP tools policy_show, policy_history, policy_diff, policy_set, freeze_list, freeze_start and freeze_end. For the full command and endpoint references, see the CLI Reference and the API Reference.