Skip to content
bucker

Releases, deploys and release health

A release is a version string your code reports (release on every event, and on every session). Registering it, and recording where it was deployed, is what turns that string into "what shipped, who wrote it, and where is it live".

# Register the release with the commit it was built from.
curl -X POST "$API/orgs/$ORG/projects/$PROJECT/releases" \
  -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"version":"4.2.0","commitSha":"<40-hex sha>"}'

# Record a deploy of it.
curl -X POST "$API/orgs/$ORG/projects/$PROJECT/releases/4.2.0/deploys" \
  -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"environment":"production"}'

Or, from CI, with the bucker CLI (cli.md), which does the same with retries, source maps and a verify gate:

bucker releases new "$VERSION" --commit "$GIT_SHA"
bucker sourcemaps upload dist --release "$VERSION"   # fails the job if a shipped id cannot resolve
bucker releases finalize "$VERSION"
bucker deploys new "$VERSION" --env production

CI recipes

All three recipes run the four commands above, in that order. Each takes the token from the CI system's secret store. Use an org CI token (bkr_org_…, Settings → Tokens, scopes project:read and release:write): it is not bound to a person, so the pipeline keeps working after they leave. The files are in code/tooling/release-action/, and tooling/bucker-cli/test/ci-recipes.test.ts runs each command in them through the real CLI.

GitHub Actions: the composite action bucker/release-action:

- run: npm run build
- uses: bucker/release-action@v0
  with:
    token: ${{ secrets.BUCKER_TOKEN }}
    org: acme
    project: web
    sourcemaps: dist          # omit to skip source maps
    environment: production   # '' records no deploy

GitLab CI/CD: a CI/CD component. BUCKER_TOKEN is a masked, protected CI/CD variable:

include:
  - component: $CI_SERVER_FQDN/<group>/bucker-release/release@0.1.0
    inputs:
      org: acme
      project: web
      sourcemaps: dist

AWS CodeBuild: merge this into your buildspec.yml. The token comes from Secrets Manager:

env:
  variables: { BUCKER_ORG: acme, BUCKER_PROJECT: web, CLI: '@bucker/cli@0.1.0' }
  secrets-manager:
    BUCKER_TOKEN: bucker/ci:token
phases:
  post_build:
    commands:
      - export BUCKER_RELEASE="${BUCKER_RELEASE:-$CODEBUILD_RESOLVED_SOURCE_VERSION}"
      - npx --yes "$CLI" releases new "$BUCKER_RELEASE" --commit "$CODEBUILD_RESOLVED_SOURCE_VERSION"
      - npx --yes "$CLI" sourcemaps upload dist --release "$BUCKER_RELEASE"
      - npx --yes "$CLI" releases finalize "$BUCKER_RELEASE"
      - npx --yes "$CLI" deploys new "$BUCKER_RELEASE" --env production

What shipped in a release

When a release is registered with a commitSha, Bucker walks the commits between the previous release's commit and this one's and stores them. The release page lists them, newest first, with author, time and the files each touched. GET …/releases/{version}/commits returns the same list.

The walk uses your connected repository through the SCM provider. It needs a repository connected for the organization and a code mapping for the project (the same mapping suspect commits use). Where the provider cannot compare two commits, Bucker falls back to the commits your repository webhook already delivered between the two releases and says so on the page.

A release never shows a bare empty list. The page states why it is empty:

Status Meaning and what to do
no_commit_sha The release was registered without commitSha. Register it again with one.
no_repository No repository is mapped to the project. Connect one and add a code mapping.
consent_revoked Access to the mapped repository was revoked.
app_not_configured The repository is connected through a GitHub App installation and this deployment has no GitHub App configured (GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY). Connect the repository with a token instead.
provider_unavailable The provider could not be reached. Use Refresh (POST …/releases/{version}/sync-commits).
commit_not_found The release's commit is in none of the mapped repositories.
no_baseline No earlier release carries a commit, so only this release's own commit is known.

Deploy diff

Each deploy row has a Changes button: the commits this deploy carried into its environment, that is, the release's range minus what the environment already ran (GET …/deploys/{deployId}/diff).

Suspect commits

Once a release's commits are recorded, the suspect commits of an issue are drawn from the range of the release the issue was first seen in. A commit outside that release cannot be the cause. An issue whose first release has no recorded range keeps the 14-day window.

Resolving by release

An issue resolved without a release reopens on its very next event, including the tail of the deploy that still has the bug. Pinned to a release, only an event from a later release reopens it. There are three ways to pin without knowing the version yet.

Resolve in the next release

Resolve in release… has a Resolve in next release button. The fix is merged but not released: the issue stays resolved, nothing reopens it, and the first release registered after that moment becomes its fix. The activity feed records the moment it binds ("The next release arrived: now resolved in 4.2.0"). Over the API this is {"action":"resolve","resolvedInRelease":"@next-release"}.

A release is "registered" when POST …/releases creates it, or when the first event naming a new version arrives.

Resolve from a commit message

A push to the repository's default branch whose commit message says fixes BKR-123, closes BKR-123, BKR-124 or resolves BKR-123 resolves those issues in the next release, with an activity row naming the commit. Only the project that repository is mapped to is searched, so another project's BKR-123 is never touched. Issues already resolved keep their release, and archived issues stay archived. A bare mention (see BKR-123) resolves nothing. The commit message is what is read, which includes the title Git hosts write for a squash or merge commit. The body of a pull request is not.

Auto-resolve on deploy (opt-in)

Settings › Projects › Release settings › Auto-resolve issues quiet for (days). With a value set, recording a new deploy (POST …/releases/{version}/deploys) resolves the project's open issues that have not been seen for that many days, pinned to the deployed release; the activity row says which deploy and how long they had been quiet. Empty means off, which is the default. Archived issues are never touched. GET/PUT …/release-settings is the same setting over the API.

Release-health alerts and a stability target

Alert on crash-free rate and adoption

A metric alert rule can watch three release-health rates, through the same router, channels, debounce and history as every other alert:

Aggregate Value
crash_free_sessions 1 - crashed / sessions for the release being watched
crash_free_users 1 - crashed users / users (needs identified users; server-SDK aggregates carry none)
release_adoption the release's share of the project's sessions in the window

All three are rates between 0 and 1, so the threshold is 0.995, not 99.5. Use the lt comparator to alert when one drops:

{ "id": "metric", "aggregate": "crash_free_sessions", "comparator": "lt",
  "threshold": 0.995, "windowMinutes": 180, "minSessions": 200 }
  • Which release. The latest one in the environment: the release whose first session bucket is most recent, so the rule follows your project through its releases without being edited. A release filter (equals) pins it to one version; an environment filter narrows the sessions read. A level filter is refused, since sessions have no level.
  • No reading is not a breach. With fewer than minSessions sessions (users, for crash_free_users) in the window, default 100, the rule reports "no reading" and neither fires nor records a recovery. Two crashes in the first five sessions of a rollout is not an incident.
  • Freshness. Sessions and adoption read the same hourly rollup the release pages read, so a reading is at most one rollup pass old, and the alert and the page cannot disagree. Users are counted from the session table.
  • The anomaly (learned-band) condition is not offered for these rates.

In the rule editor, choose a metric rule and the aggregate under Aggregate.

Stability target

Settings › Projects › Release settings › Crash-free sessions target (%) is an opt-in target for the project, for example 99.5. It is shown on every release page as met or missed against that release's own crash-free session rate, and the staged-rollout guardian uses it: a rollout whose cohort has at least the sample floor of sessions and misses the target beyond chance (a one-sided test at the guardian's p-value ceiling) halts with the verdict BELOW_TARGET, with or without a baseline release to compare against. Leave it empty and the guardian judges by the baseline alone, as before. Over the API: GET/PUT …/release-settings (crashFreeTarget, a rate).

Comparing releases, and the suspect deploy

Compare with another release

The release page has a Compare with another release panel. Pick any other release (the predecessor is offered first) and the page puts the two side by side on crash-free sessions and users, adoption, issues the release introduced, issues it brought back (regressions whose event carried this release) and issues pinned as resolved in it, and the p95 duration of its root spans (the last 30 days). The change column is computed on the server: GET …/release-compare?base=&head=&environment=.

These are each release's own totals over its whole life. That is the right question for "which release was the worse one", and a different one from the comparison above it on the page, which asks whether a release is worse than its predecessor was at the same point in its rollout (the question the staged-rollout guardian halts on). A figure with nothing behind it, such as no sessions or no spans, is shown as a dash, never as zero.

Suspect deploy on an issue

The issue's rail has a Suspect deploy row: the last deploy to the environment the issue began in, at or before the moment it was first seen, the release that deploy carried, and the commits that shipped with it (see above). It says how sure it is: whether the issue's first event carried that release, and how long the deploy preceded it, so a deploy from last week reads as the coincidence it probably is. It is a pointer to where to look, not a verdict. The row is absent for a project that records no deploys. GET …/issues/{id}/suspect-deploy returns the same.

Release health by OS, browser, device and environment

The release page has a Health by segment panel: the release's sessions grouped by operating system, browser, device class (desktop, mobile, tablet, bot, other) or environment, each row with its sessions, share, crash-free sessions and crash-free users, every rate beside its denominator. The same grouping is GET …/release-health/{version}?groupBy=os|browser|device|environment (a segments object on the usual response), and the crash-free rate dashboard widget takes a Break down by setting that adds the same rows.

  • Where the dimensions come from. A session item's attrs.user_agent. The browser SDK sends navigator.userAgent; the server reads the OS, browser family and device class from it when the session arrives and does not keep the string. Versions are not kept either: "Chrome 124.0.6367.91" is thousands of groups of one, and a group of one is not a crash-free rate. Sentry's browser SDKs send attrs.user_agent too, so they segment with no change.
  • Unknown is a row. A session that sent no User-Agent (an older SDK, a runtime that is not a browser) and every session recorded before this existed is Unknown, so the rows always add up to the release's sessions and you can see how much of it was measurable. There is no backfill.
  • Aggregate sessions have no device. A Sentry server SDK in request mode sends counts per minute (sessions items, stored in release_session_aggregates), not sessions, so there is no User-Agent or device behind them. They are in the release's totals and in the environment grouping (which they do carry), and in no OS, browser or device row. The panel and the API (excludedAggregatedSessions) say how many, rather than guessing a split.
  • iPad. iPadOS 13+ identifies itself as a Mac and is counted as one: nothing in the User-Agent tells them apart.