Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions .github/workflows/sentry-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,19 @@ jobs:
# always run on manual dispatch.
if: github.event_name == 'workflow_dispatch' || !github.event.release.prerelease
env:
# Secret is scoped to the "production" environment — the job needs
# `environment: production` above to access it.
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
# Tag names are bare semver (e.g., "0.24.0", no "v" prefix),
# matching both the npm package version and Sentry release version.
# matching the Sentry.init() release value and sourcemap uploads.
VERSION: ${{ github.event.release.tag_name || inputs.version }}
steps:
# Full checkout needed for set-commits --auto (discovers git remote + HEAD).
- uses: actions/checkout@v6
with:
fetch-depth: 0

# The sentry npm package requires Node.js >= 22 (ubuntu-latest ships 20).
- name: Setup Node.js
uses: actions/setup-node@v6
with:
Expand All @@ -35,9 +43,16 @@ jobs:
- name: Install CLI
run: npm install -g "sentry@${VERSION}"

# "sentry/${VERSION}" = org-slug/version (e.g., org=sentry, version=0.24.0).
# The org/ prefix is how org is specified — it is NOT part of the version.
# The version portion must match Sentry.init({ release }) exactly.
- name: Create release
run: sentry release create "sentry/${VERSION}" --project cli
run: >-
sentry release create "sentry/${VERSION}" --project cli
--url "https://github.com/${{ github.repository }}/releases/tag/${VERSION}"

# --auto matches the local origin remote against Sentry repo integrations.
# continue-on-error: integration may not be configured for all orgs.
- name: Set commits
continue-on-error: true
run: sentry release set-commits "sentry/${VERSION}" --auto
Expand Down
34 changes: 34 additions & 0 deletions docs/src/content/docs/agent-guidance.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,38 @@ sentry span list my-org/my-project/abc123def456...

When querying the Events API (directly or via `sentry api`), valid dataset values are: `spans`, `transactions`, `logs`, `errors`, `discover`.

## Release Workflow

The `sentry release` command group manages Sentry releases for tracking deploys and associating commits with errors. A typical CI workflow:

```bash
# Create a release (version must match Sentry.init() release value)
sentry release create my-org/1.0.0 --project my-project

# Associate commits via repository integration (requires git checkout)
sentry release set-commits my-org/1.0.0 --auto

# Mark the release as finalized
sentry release finalize my-org/1.0.0

# Record a deploy
sentry release deploy my-org/1.0.0 production
```

**Key details:**

- The `org/version` positional is `<org-slug>/<version>`, NOT a version prefix. `sentry release create sentry/1.0.0` means org=`sentry`, version=`1.0.0`. This is how org is specified — not via `SENTRY_ORG`.
- The release **version** (e.g., `1.0.0`) must match the `release` value in your `Sentry.init()` call. If your SDK uses bare semver, the release must be bare semver too.
- `--auto` requires **both** a Sentry repository integration (GitHub/GitLab/Bitbucket) **and** a local git checkout. It lists repos from the API and matches against your local `origin` remote URL, then sends the HEAD commit SHA. Without a checkout, use `--local` instead.
- When neither `--auto` nor `--local` is specified, the CLI tries `--auto` first and falls back to `--local` on failure.

### CI/CD Setup Notes

- The `sentry` npm package requires **Node.js >= 22**. CI runners like `ubuntu-latest` ship Node.js 20 — add `actions/setup-node@v6` with `node-version: 22`.
- If `SENTRY_AUTH_TOKEN` is scoped to a GitHub environment (e.g., `production`), set `environment: production` on the job.
- A full git checkout (`fetch-depth: 0`) is needed for `--auto` to discover the remote URL and HEAD.
- `set-commits --auto` has `continue-on-error` in most workflows because it requires a working repository integration. If the integration isn't configured, the step fails but the rest of the release workflow succeeds.

## Common Mistakes

- **Wrong issue ID format**: Use `PROJECT-123` (short ID), not the numeric ID `123456789`. The short ID includes the project prefix.
Expand All @@ -192,4 +224,6 @@ When querying the Events API (directly or via `sentry api`), valid dataset value
- **Confusing `--query` syntax**: The `--query` flag uses Sentry search syntax (e.g., `is:unresolved`, `assigned:me`), not free text search.
- **Not using `--web`**: View commands support `-w`/`--web` to open the resource in the browser — useful for sharing links.
- **Fetching API schemas instead of using the CLI**: Prefer `sentry schema` to browse the API and `sentry api` to make requests — the CLI handles authentication and endpoint resolution, so there's rarely a need to download OpenAPI specs separately.
- **Release version mismatch**: The `org/version` positional is `<org-slug>/<version>`, where `org/` is the org, not part of the version. `sentry release create sentry/1.0.0` creates version `1.0.0` in org `sentry`. If your `Sentry.init()` uses `release: "1.0.0"`, this is correct. Don't double-prefix like `sentry/myapp/1.0.0`.
- **Running `set-commits --auto` without a git checkout**: `--auto` needs a local git repo to discover the origin remote URL and HEAD commit. In CI, ensure `actions/checkout` with `fetch-depth: 0` runs before `set-commits --auto`.
- **Using `sentry api` when CLI commands suffice**: `sentry issue list --json` already includes `shortId`, `title`, `priority`, `level`, `status`, `permalink`, and other fields at the top level. Some fields like `count`, `userCount`, `firstSeen`, and `lastSeen` may be null depending on the issue. Use `--fields` to select specific fields and `--help` to see all available fields. Only fall back to `sentry api` for data the CLI doesn't expose.
16 changes: 15 additions & 1 deletion docs/src/content/docs/commands/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ Set commits for a release

| Option | Description |
|--------|-------------|
| `--auto` | Use repository integration to auto-discover commits |
| `--auto` | Auto-discover commits via repository integration (needs local git checkout) |
| `--local` | Read commits from local git history |
| `--clear` | Clear all commits from the release |
| `--commit <commit>` | Explicit commit as REPO@SHA or REPO@PREV..SHA (comma-separated) |
Expand Down Expand Up @@ -189,4 +189,18 @@ sentry release create $(sentry release propose-version)
# Output as JSON
sentry release list --json
sentry release view 1.0.0 --json

# CI/CD: full release workflow with org prefix
sentry release create my-org/1.0.0 --project my-project --url "https://github.com/org/repo/releases/tag/1.0.0"
sentry release set-commits my-org/1.0.0 --auto
sentry release finalize my-org/1.0.0
sentry release deploy my-org/1.0.0 production
```

## Important Notes

- **Version matching**: The release version must match the `release` value in your `Sentry.init()` call. If your SDK uses `"1.0.0"`, create the release as `sentry release create org/1.0.0` (version = `1.0.0`), **not** `sentry release create org/myapp/1.0.0`.
- **The `org/` prefix is the org slug**: In `sentry release create sentry/1.0.0`, `sentry` is the org slug and `1.0.0` is the version. The `/` separates org from version, it's not part of the version string.
- **`--auto` needs a git checkout**: The `--auto` flag lists repos from the Sentry API and matches against your local `origin` remote URL. A full checkout (`git fetch-depth: 0`) is needed for `--auto` to work. Without a checkout, use `--local`.
- **Default mode tries `--auto` first**: When neither `--auto` nor `--local` is specified, the CLI tries auto-discovery first and falls back to local git history if the integration isn't configured.
- **Node.js >= 22 required**: The `sentry` npm package requires Node.js 22 or later. CI runners like `ubuntu-latest` ship Node.js 20 by default.
34 changes: 34 additions & 0 deletions plugins/sentry-cli/skills/sentry-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,38 @@ sentry span list my-org/my-project/abc123def456...

When querying the Events API (directly or via `sentry api`), valid dataset values are: `spans`, `transactions`, `logs`, `errors`, `discover`.

### Release Workflow

The `sentry release` command group manages Sentry releases for tracking deploys and associating commits with errors. A typical CI workflow:

```bash
# Create a release (version must match Sentry.init() release value)
sentry release create my-org/1.0.0 --project my-project

# Associate commits via repository integration (requires git checkout)
sentry release set-commits my-org/1.0.0 --auto

# Mark the release as finalized
sentry release finalize my-org/1.0.0

# Record a deploy
sentry release deploy my-org/1.0.0 production
```

**Key details:**

- The `org/version` positional is `<org-slug>/<version>`, NOT a version prefix. `sentry release create sentry/1.0.0` means org=`sentry`, version=`1.0.0`. This is how org is specified — not via `SENTRY_ORG`.
- The release **version** (e.g., `1.0.0`) must match the `release` value in your `Sentry.init()` call. If your SDK uses bare semver, the release must be bare semver too.
- `--auto` requires **both** a Sentry repository integration (GitHub/GitLab/Bitbucket) **and** a local git checkout. It lists repos from the API and matches against your local `origin` remote URL, then sends the HEAD commit SHA. Without a checkout, use `--local` instead.
- When neither `--auto` nor `--local` is specified, the CLI tries `--auto` first and falls back to `--local` on failure.

#### CI/CD Setup Notes

- The `sentry` npm package requires **Node.js >= 22**. CI runners like `ubuntu-latest` ship Node.js 20 — add `actions/setup-node@v6` with `node-version: 22`.
- If `SENTRY_AUTH_TOKEN` is scoped to a GitHub environment (e.g., `production`), set `environment: production` on the job.
- A full git checkout (`fetch-depth: 0`) is needed for `--auto` to discover the remote URL and HEAD.
- `set-commits --auto` has `continue-on-error` in most workflows because it requires a working repository integration. If the integration isn't configured, the step fails but the rest of the release workflow succeeds.

### Common Mistakes

- **Wrong issue ID format**: Use `PROJECT-123` (short ID), not the numeric ID `123456789`. The short ID includes the project prefix.
Expand All @@ -202,6 +234,8 @@ When querying the Events API (directly or via `sentry api`), valid dataset value
- **Confusing `--query` syntax**: The `--query` flag uses Sentry search syntax (e.g., `is:unresolved`, `assigned:me`), not free text search.
- **Not using `--web`**: View commands support `-w`/`--web` to open the resource in the browser — useful for sharing links.
- **Fetching API schemas instead of using the CLI**: Prefer `sentry schema` to browse the API and `sentry api` to make requests — the CLI handles authentication and endpoint resolution, so there's rarely a need to download OpenAPI specs separately.
- **Release version mismatch**: The `org/version` positional is `<org-slug>/<version>`, where `org/` is the org, not part of the version. `sentry release create sentry/1.0.0` creates version `1.0.0` in org `sentry`. If your `Sentry.init()` uses `release: "1.0.0"`, this is correct. Don't double-prefix like `sentry/myapp/1.0.0`.
- **Running `set-commits --auto` without a git checkout**: `--auto` needs a local git repo to discover the origin remote URL and HEAD commit. In CI, ensure `actions/checkout` with `fetch-depth: 0` runs before `set-commits --auto`.
- **Using `sentry api` when CLI commands suffice**: `sentry issue list --json` already includes `shortId`, `title`, `priority`, `level`, `status`, `permalink`, and other fields at the top level. Some fields like `count`, `userCount`, `firstSeen`, and `lastSeen` may be null depending on the issue. Use `--fields` to select specific fields and `--help` to see all available fields. Only fall back to `sentry api` for data the CLI doesn't expose.

## Prerequisites
Expand Down
8 changes: 7 additions & 1 deletion plugins/sentry-cli/skills/sentry-cli/references/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ List deploys for a release
Set commits for a release

**Flags:**
- `--auto - Use repository integration to auto-discover commits`
- `--auto - Auto-discover commits via repository integration (needs local git checkout)`
- `--local - Read commits from local git history`
- `--clear - Clear all commits from the release`
- `--commit <value> - Explicit commit as REPO@SHA or REPO@PREV..SHA (comma-separated)`
Expand Down Expand Up @@ -120,6 +120,12 @@ sentry release create $(sentry release propose-version)
# Output as JSON
sentry release list --json
sentry release view 1.0.0 --json

# CI/CD: full release workflow with org prefix
sentry release create my-org/1.0.0 --project my-project --url "https://github.com/org/repo/releases/tag/1.0.0"
sentry release set-commits my-org/1.0.0 --auto
sentry release finalize my-org/1.0.0
sentry release deploy my-org/1.0.0 production
```

All commands also support `--json`, `--fields`, `--help`, `--log-level`, and `--verbose` flags.
4 changes: 4 additions & 0 deletions src/commands/release/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,17 @@ export const createCommand = buildCommand({
brief: "Create a release",
fullDescription:
"Create a new Sentry release.\n\n" +
"The version must match the `release` value in Sentry.init().\n" +
"Use `org/version` to specify the org — the `org/` prefix is the org slug, not\n" +
"part of the version. E.g., `sentry/1.0.0` means org=sentry, version=1.0.0.\n\n" +
"Examples:\n" +
" sentry release create 1.0.0\n" +
" sentry release create my-org/1.0.0\n" +
" sentry release create 1.0.0 --project my-project\n" +
" sentry release create 1.0.0 --project proj-a,proj-b\n" +
" sentry release create 1.0.0 --finalize\n" +
" sentry release create 1.0.0 --ref main\n" +
" sentry release create 1.0.0 --url https://github.com/org/repo/releases/tag/1.0.0\n" +
" sentry release create 1.0.0 --dry-run",
},
output: {
Expand Down
20 changes: 15 additions & 5 deletions src/commands/release/set-commits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ async function setCommitsDefault(
}

try {
const release = await setCommitsAuto(org, version);
const release = await setCommitsAuto(org, version, cwd);
clearRepoIntegrationCache(org);
return release;
} catch (error) {
Expand All @@ -142,6 +142,13 @@ async function setCommitsDefault(
);
return setCommitsFromLocal(org, version, cwd, depth);
}
if (error instanceof ValidationError) {
log.warn(
`Auto-discovery failed: ${error.message}. ` +
"Falling back to local git history."
);
return setCommitsFromLocal(org, version, cwd, depth);
}
throw error;
}
}
Expand All @@ -162,8 +169,10 @@ export const setCommitsCommand = buildCommand({
brief: "Set commits for a release",
fullDescription:
"Associate commits with a release.\n\n" +
"Use --auto to let Sentry discover commits via your repository integration,\n" +
"or --local to read commits from the local git history.\n\n" +
"Use --auto to let Sentry discover commits via your repository integration\n" +
"(requires a local git checkout — matches the origin remote against Sentry repos),\n" +
"or --local to read commits from the local git history.\n" +
"With no flag, tries --auto first and falls back to --local on failure.\n\n" +
"Examples:\n" +
" sentry release set-commits 1.0.0 --auto\n" +
" sentry release set-commits my-org/1.0.0 --local\n" +
Expand All @@ -186,7 +195,8 @@ export const setCommitsCommand = buildCommand({
flags: {
auto: {
kind: "boolean",
brief: "Use repository integration to auto-discover commits",
brief:
"Auto-discover commits via repository integration (needs local git checkout)",
default: false,
},
local: {
Expand Down Expand Up @@ -312,7 +322,7 @@ export const setCommitsCommand = buildCommand({
);
} else if (flags.auto) {
// Explicit --auto: use repo integration, fail hard on error
release = await setCommitsAuto(resolved.org, version);
release = await setCommitsAuto(resolved.org, version, cwd);
} else {
// Default (no flag): try auto with cached fallback
release = await setCommitsDefault(
Expand Down
83 changes: 68 additions & 15 deletions src/lib/api/releases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@ import {
updateAnOrganization_sRelease,
} from "@sentry/api";

import { ApiError, ValidationError } from "../errors.js";
import { getHeadCommit, getRepositoryName } from "../git.js";
import { resolveOrgRegion } from "../region.js";
import {
API_MAX_PER_PAGE,
apiRequestToRegion,
getOrgSdkConfig,
MAX_PAGINATION_PAGES,
type PaginatedResponse,
unwrapPaginatedResult,
unwrapResult,
} from "./infrastructure.js";
import { listRepositoriesPaginated } from "./repositories.js";

// We cast through `unknown` to bridge the gap between the SDK's internal
// return types and the public response types — the shapes are compatible
Expand Down Expand Up @@ -269,32 +274,80 @@ export async function createReleaseDeploy(
/**
* Set commits on a release using auto-discovery mode.
*
* This uses the internal API format `refs: [{repository: "auto", commit: "auto"}]`
* which is not part of the OpenAPI spec, so we use apiRequestToRegion directly.
* Lists the org's repositories from the Sentry API, matches against the
* local git remote URL to find the corresponding Sentry repo, then sends
* a refs payload with the HEAD commit SHA. This is the equivalent of the
* reference sentry-cli's `--auto` mode.
*
* Requires a GitHub/GitLab/Bitbucket integration configured in Sentry.
* Requires a GitHub/GitLab/Bitbucket integration configured in Sentry
* AND a local git repository whose origin remote matches a Sentry repo.
*
* @param orgSlug - Organization slug
* @param version - Release version
* @param cwd - Working directory to discover git remote and HEAD from
* @returns Updated release detail with commit count
* @throws {ApiError} When the org has no repository integrations (400)
* @throws {ValidationError} When local git remote is missing or doesn't match any Sentry repo
*/
export async function setCommitsAuto(
orgSlug: string,
version: string
version: string,
cwd?: string
): Promise<OrgReleaseResponse> {
const regionUrl = await resolveOrgRegion(orgSlug);
const encodedVersion = encodeURIComponent(version);
const { data } = await apiRequestToRegion<OrgReleaseResponse>(
regionUrl,
`organizations/${orgSlug}/releases/${encodedVersion}/`,
{
method: "PUT",
body: {
refs: [{ repository: "auto", commit: "auto" }],
},
const localRepo = getRepositoryName(cwd);
if (!localRepo) {
throw new ValidationError(
"Could not determine repository name from local git remote.",
"repository"
);
}

// Paginate through org repos to find one matching the local git remote.
// Stops as soon as a match is found to avoid unnecessary API calls.
const localRepoLower = localRepo.toLowerCase();
let cursor: string | undefined;
let foundAnyRepos = false;

for (let page = 0; page < MAX_PAGINATION_PAGES; page++) {
const result = await listRepositoriesPaginated(orgSlug, {
cursor,
perPage: API_MAX_PER_PAGE,
});

if (result.data.length > 0) {
foundAnyRepos = true;
}

const match = result.data.find(
(r) => r.name.toLowerCase() === localRepoLower
);
if (match) {
const headCommit = getHeadCommit(cwd);
return setCommitsWithRefs(orgSlug, version, [
{ repository: match.name, commit: headCommit },
]);
}

if (!result.nextCursor) {
break;
}
cursor = result.nextCursor;
}

if (!foundAnyRepos) {
const endpoint = `organizations/${orgSlug}/releases/${encodeURIComponent(version)}/`;
throw new ApiError(
"No repository integrations configured for this organization.",
400,
undefined,
endpoint
);
}

throw new ValidationError(
`No Sentry repository matching '${localRepo}'.`,
"repository"
);
return data;
}

/**
Expand Down
Loading
Loading