Skip to content

feat(api): Move the @sentry/api package into static/packages/api - #130472

Open
sentry-junior[bot] wants to merge 16 commits into
masterfrom
feat/api-package
Open

sentry-junior[bot] wants to merge 16 commits into
masterfrom
feat/api-package

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Moves @sentry/api, the generated TypeScript client for Sentry's public API, from getsentry/sentry-api-schema to static/packages/api. It builds from tests/apidocs/openapi-derefed.json, the spec this repository generates. The npm name stays @sentry/api, so getsentry/toolkit, sentry-docs and other consumers don't change. This PR replaces the pre-merge check from #130450, which was closed.

This PR doesn't publish anything. Releases from here stay off until a person cuts the first api@ tag (see the cutover section).

Package

  • The sources match getsentry/sentry-api-schema main at 2.3.1, including the @sentry/api/docs API reference export (feat(api): Export API reference data as @sentry/api/docs sentry-api-schema#150), the listPage per_page fix (fix(api): Keep per_page stable when listPage walks offset cursors sentry-api-schema#149) and insufficient_scope handling on ApiPermissionError (feat(api-client): Carry the scopes a 403 insufficient_scope challenge accepts sentry-api-schema#146).
  • One deliberate difference from sentry-api-schema main: the generated buildUrl trims trailing slashes from the base URL before appending /api/0 (withApiBasePath in lib/api-client/api-base-url.ts), so https://sentry.io/ no longer requests https://sentry.io//api/0. Published 2.x versions keep that bug; the fix ships with the first api@ release from here.
  • package.json is at 2.3.1, the latest npm version. Craft bumps from it while there is no api@ tag, so the first release from here lands after sentry-api-schema's.
  • Differences from sentry-api-schema, all because of where the package now lives:
    • TypeScript 6 through the repo's @typescript/typescript6 alias, and zod 4.6.7, the version static/app, gsApp, gsAdmin and scraps already pin. The other dev tools are pinned to exact versions, and oxlint matches the repo's 1.90.0.
    • The build reads the spec files through lib/spec-files.mjs and collects them under spec/.
    • source-analysis uses a path-parameter pattern that can't backtrack on runs of {, for a CodeQL alert the move raised.
    • The scripts are test:vitest and lint:js, not test and lint. The repo's test-ci and test-all pass Jest flags to every workspace's test script, and the repo's lint:js runs each workspace's lint:js.

Spec files on GitHub releases

Each api@x.y.z GitHub release gets the files that version was built from as assets. The pack job adds them to the release artifact; craft's github target attaches every file in it, and its npm target publishes only the tarball:

  • openapi-derefed.json, the dereferenced OpenAPI spec.
  • rpc_method_schema.json, for the openapi-diff/RPC check. It comes from a new make build-rpc-schema target.
  • api_ownership_stats_dont_modify.json, which make build-api-docs still generates. No known consumer reads it; it is kept for ad-hoc and future use.

For example: https://github.com/getsentry/sentry/releases/download/api@x.y.z/openapi-derefed.json. The npm package doesn't include them.

CI: api client is a required check

The new api-client job in backend.yml builds, typechecks, lints and tests the client against the PR's own spec. It is part of backend-required-check. A spec change that broke the client once stalled releases for 2 days with nothing catching it before merge; this check catches that class of change.

  • It reuses the spec from the api-docs job (shared as a workflow artifact), so the spec isn't built twice.
  • Both jobs run only when the new api_client filter matches: the existing api_docs paths plus static/packages/api/**. On other PRs both jobs are skipped, and backend-required-check counts skipped jobs as passing.

Releases

  • Pack: package-pack.yml sets up the Python env for api, then builds the spec, the RPC schema and the package before pnpm pack.
  • Release on change: a release-api-package job in openapi.yml runs after the master spec build whenever the spec or the package might have changed. It runs .github/workflows/scripts/api-package-release-preflight.sh, then craft prepare with getsentry/publish. The preflight skips the release when:
    • there is no api@ tag yet, so the job stays inert until the first release is cut by hand;
    • an api/release/* branch is still pending. When that release merges back, its own run releases whatever landed in the meantime;
    • HEAD predates the newest api@ tag;
    • the spec, the RPC schema and the package are unchanged since the last release. It compares the files against the last release's assets.
  • Version bump: a spec change is a minor release, as in sentry-api-schema. A package change uses craft's automatic bump. An RPC-schema-only change is a patch. An ownership-stats-only change doesn't release; the file refreshes with the next release.
  • Concurrency: releases share a concurrency group with cancel-in-progress: false, so a burst of merges produces one release.
  • There is no cron. Manual releases go through the Release workflow, which gets a static/packages/api option.
  • sentry-docs: bump-sentry-docs.yml runs on each published api@ release. .craft.yml lists the npm target before github for static/packages/*, so the version is already on npm when the release is published (the same order applies to icons, scraps and search-syntax, and nothing in them needs the tag before the npm publish). It dispatches sentry-docs' bump-api-schema-sha.yml by file name with the existing SENTRY_API_SCHEMA_UPDATER app. sentry-docs' bump reads the version from npm, so it works the same after the move.
  • The mirror push to getsentry/sentry-api-schema still runs, on the same api_client filter, and the release doesn't wait for it.

Cutover (in this order)

  1. Merge ci(openapi-diff): Compare API schemas with the PR's merge-base #130541 and getsentry/eng-pipes#1116, which retires the apis command. ci(openapi-diff): Compare API schemas with the PR's merge-base #130541 falls back to sentry-api-schema main until an api@ release exists.
  2. Merge this PR.
  3. In getsentry/publish auto-approve-repos.txt, replace the getsentry/sentry-api-schema line with this repo's static/packages/api entry.
  4. Disable sentry-api-schema's Release workflow and its sentry-docs dispatch workflow (ci: Dispatch the sentry-docs @sentry/api bump on each release sentry-api-schema#155), so only one repo publishes.
  5. Cut the first api@ release by hand: run the Release workflow with workspace static/packages/api. Check that the version it picks is above sentry-api-schema's last npm release. From then on, openapi.yml releases on change.
  6. Remove the openapi-diff fallback, the mirror push in openapi.yml, and the sentry-api-schema links in the getsentry/toolkit docs, getsentry/skills document-api-endpoint and devinfra-mcp docs/sentry/ci.md.
  7. Archive getsentry/sentry-api-schema. Its copies of the SENTRY_API_SCHEMA_UPDATER_* credentials can go with it.

static/packages/api/ is owned by @getsentry/owners-api and @getsentry/ocpo in CODEOWNERS.

This PR touches static/ as well as backend CI files. The package is a standalone npm package and isn't part of either deploy, so splitting it wouldn't change what gets deployed.

No UI change.

via @BYK.

--

View Session [Sentry]

@sentry-junior
sentry-junior Bot requested review from a team as code owners November 9, 2026 19:10
@sentry-junior
sentry-junior Bot requested review from a team as code owners November 9, 2026 19:10
@github-actions github-actions Bot added Scope: Frontend Automatically applied to PRs that change frontend components Scope: Backend Automatically applied to PRs that change backend components labels Nov 9, 2026
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

🚨 Warning: This pull request contains Frontend and Backend changes!

It's discouraged to make changes to Sentry's Frontend and Backend in a single pull request. The Frontend and Backend are not atomically deployed. If the changes are interdependent of each other, they must be separated into two pull requests and be made forward or backwards compatible, such that the Backend or Frontend can be safely deployed independently.

Have questions? Please ask in the #discuss-dev-infra channel.

@sentry

sentry Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Sentry Snapshot Testing

Name Added Removed Changed Renamed Unchanged Skipped Status
sentry-frontend
sentry-frontend
0 0 0 0 934 0 ✅ Unchanged

⚙️ sentry-frontend Snapshot Settings

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread knip.config.ts Resolved
BYK added a commit to getsentry/sentry-api-schema that referenced this pull request Nov 13, 2026
Ported from getsentry/sentry#130518, per @BYK's request there: get the
docs aligned here first, then move `@sentry/api` into getsentry/sentry.
The commit applied cleanly on top of 2.0.0. Cursor Bugbot found the bug
while reviewing the move
([thread](getsentry/sentry#130472 (comment))).
It ships in the published `@sentry/api` 2.0.0.

## Bug

`listPage` asked each later page for only the rows still missing:
`per_page = min(100, limit - items.length)`. `listProjects`, `listTeams`
and `listOrganizations` are served by Sentry's `OffsetPaginator`. Its
cursor is `<page size>:<page>:<is_prev>`. When `per_page` doesn't match
the cursor's page size, it reads the cursor's window and keeps only the
last `per_page` rows (`src/sentry/api/paginator.py` in
getsentry/sentry). So `100:1:0` with `per_page=50` returns rows 150–199,
and rows 100–149 are lost. Any `limit` above 100 dropped rows without an
error.

## Fix

`listPage` now walks through the package's existing `paginate`, which:
- uses one `per_page` for the whole walk. A fresh walk splits `limit`
evenly: 150 becomes 2 × 75.
- reuses the page size that an offset cursor encodes when resuming,
whatever the new `limit` is.
- returns a cursor that resumes at the first row not returned, including
partway through a page.

`listPage` still validates each page with the schema, and still rejects
a page with more rows than requested or a repeated cursor. One behavior
changes: if a walk reaches `MAX_LIST_PAGES`, it now returns the rows it
has and a resume cursor instead of throwing. Before this change, a
stable page size couldn't hit that cap in a normal walk. Now a resumed
small-page cursor can, for example `25:1:0` with `limit: 5000`.

## Tests

- `test/api-client/core.test.ts` models `OffsetPaginator`. It confirms
that `100:1:0` with `per_page=50` reads rows 150–199. Then it checks two
things:
- a 150-row walk and its resume return rows 0–299 in order, all with
`per_page=75`;
- resuming `100:1:0` with `limit: 50` returns rows 100–149, then
150–199, both requested with `per_page=100`.
- The `listOrganizations` test now expects `per_page` to stay at 3 on
the second request. It was 1, which was the bug.

`pnpm build`, `pnpm typecheck` and `pnpm test` pass locally (53 files,
675 tests).

No UI change.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:agent%3A69da19b385017e68b7f9eace56e9477f -->

--

[View
Session](https://junior-prod.sentry.dev/conversations/agent%3A69da19b385017e68b7f9eace56e9477f)
[[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3A69da19b385017e68b7f9eace56e9477f/?project=4510944073809921)

<!-- junior-session-footer:end -->

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
sentry-junior Bot and others added 6 commits November 13, 2026 18:43
Move the generated TypeScript client for Sentry's public API from
getsentry/sentry-api-schema into this repository, next to the spec it is
built from.

- Build it from tests/apidocs/openapi-derefed.json, upgraded to TypeScript 6
  with exact dev tool pins and zod 4.6.7.
- Ship the spec, the API ownership stats and the RPC method schema under
  @sentry/api/spec/ so consumers can read them from npm.
- Add a required api-client job to backend.yml that builds, typechecks and
  tests the client on spec or package changes.
- Pack it in package-pack.yml and release it from openapi.yml when what it
  ships changes. Releases stay off until the first api@ tag is cut by hand.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
Stop the path parameter pattern from backtracking on runs of '{', and write
the generated-code match in build step generate-request-preparation as a plain
string so CodeQL does not read its template escapes as regex escapes.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
Keep the npm package at its 1.54.0 size. The pack job adds the spec, API
ownership stats and RPC schema to the release artifact, so craft's github
target attaches them to each api@ release while npm takes only the tarball.
The release preflight now compares against those release assets.

Also make ocpo a code owner of static/packages/api alongside owners-api.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
…c files

Treating a release without its spec files as "everything changed" would
cut a minor release with nothing in it. Craft attaches the files before it
merges the release branch, so this only happens when something went wrong,
and a failed run is easier to notice than an empty release.
Sentry's spec now declares /api/0 in its server URL and ships relative
paths (#131065), so the build no longer found the /api/0-prefixed routes
it expected, such as the resizable pagination routes. Port
getsentry/sentry-api-schema#148: read the base path from the server URL
(or the paths, for older specs), export it as API_BASE_PATH, and make
operation paths relative to it.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

sentry-junior Bot and others added 3 commits November 13, 2026 23:47
Each published api@ release waits for its npm version, then dispatches
sentry-docs' Bump @sentry/api workflow by file name. Craft runs the
github target before npm for static/packages/*, so the wait keeps the
bump from reading the previous version. This replaces the dispatch in
getsentry/sentry-api-schema at cutover.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
Brings static/packages/api level with getsentry/sentry-api-schema main
(2.3.1):

- @sentry/api/docs exports the API reference data (getsentry/sentry-api-schema#150), with the
  no-typescript-source-strings lint rule, which the api client CI job
  now runs.
- listPage keeps per_page stable across offset cursors (getsentry/sentry-api-schema#149).
- ApiPermissionError carries the scopes from a 403 insufficient_scope
  challenge (getsentry/sentry-api-schema#146).
- Union fields and annotated single-member allOf fields keep their own
  descriptions.

The version moves to 2.3.1, so craft's first api@ release bumps from
sentry-api-schema's latest npm version. The release check no longer cuts
a patch for API ownership stats alone: the asset stays on each release
for ad-hoc use, but nothing reads it on a schedule.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
…paration

The port brought back the template literal that CodeQL misreads as a
regular expression. The plain string has the same value.
BYK added a commit that referenced this pull request Nov 14, 2026
`openapi-diff` compared every backend PR with
getsentry/sentry-api-schema `main`, which is the latest master and not
where the PR branched off, so its diff also showed unrelated master
changes. Its `json-diff` step exits 1 on any difference, so the job
failed on every PR that changes the spec. That's why develop-docs calls
the failure expected, and also why the RPC breaking-change step after it
was **skipped on exactly those PRs**.

In the 100 runs I sampled, 69 hit the `api_docs` filter. 27 failed at
json-diff, and the RPC check was skipped in all 27. The other 42 ran the
RPC check and passed. #130472 moves `@sentry/api` into
`static/packages/api`, which gives master a second baseline source
besides sentry-api-schema: the `api@` release assets.

## What changes

- **Baselines come from the PR's merge-base.** `openapi.yml` now uploads
`tests/apidocs/openapi-derefed.json` and `rpc_method_schema.json` from
each master build as an `api-schemas` artifact, kept for 30 days.
`scripts/openapi-diff.sh baseline` gets the merge-base from the compare
API and walks back up to 30 master commits to the closest one with that
artifact. Every commit that can change the schemas uploads one.
Fallbacks, in order:
1. the newest `api@` GitHub release, picked by listing releases, since
`releases/latest` is sentry's calver release;
2. sentry-api-schema `main`, for as long as it still receives the spec.

The job summary says which baseline it used. Each source writes to a
scratch directory, so a source that fails halfway leaves nothing behind.
- **The public spec diff is a changelog.** The `changelog` job writes an
`oasdiff changelog --format markdown` to the job summary, capped below
GitHub's 1 MiB limit, and never fails. A missing baseline or an oasdiff
error becomes a note in the summary.
- **The RPC check runs in its own job.** The `rpc-breaking` job runs
`oasdiff breaking --fail-on WARN` on the RPC schema, so nothing upstream
can skip it. Control and cell silos deploy separately, and the new
required `api-client` check (#130472) doesn't cover RPC.
- **One build.** The `build` job still builds the spec and RPC schema
once, with `setup-sentry`. The two compare jobs only download its
artifact and run the oasdiff image, so CI cost stays about the same.
- **`make diff-api-docs`** runs the same script: the merge-base
baseline, then the changelog. It needs gh and docker. That replaces
`api-docs/openapi-diff.ts` and its `json-diff` dependency, so the
lockfile only loses the `json-diff` subtree.
- A new `make build-rpc-schema` writes
`.artifacts/rpc_method_schema.json`. It's the same target #130472 adds.
- A shellcheck pre-commit hook (`--shell=sh
--enable=check-extra-masked-returns`) covers the new script.

The develop-docs text that says this check is expected to fail is
updated in getsentry/sentry-docs#20679, which already rewrites that
section.

## Interaction with #130472

This PR is off master and doesn't depend on #130472, but the two touch
neighboring lines in `openapi.yml` and `Makefile`.
- **If #130472 lands first:** keep its `build-rpc-schema` target, and
point the `api-schemas` upload at `static/packages/api/spec/` or
`.artifacts/rpc_method_schema.json`. The baseline script finds both
files by name inside the artifact, so their paths don't matter.
- **If this one lands first:** #130472 resolves the same two spots.

## Verification

- shellcheck (`--shell=sh --enable=check-extra-masked-returns`) is
clean. actionlint reports nothing new; its only finding is the existing
`ubuntu-26.04-arm` label. knip is clean.
- **Baseline lookup, run for real under dash** with #130472's base and
head SHAs: no `api-schemas` artifact exists yet and there's no `api@`
release, so it fell back to sentry-api-schema `main` (15.8 MB spec plus
RPC schema) in 18 s.
- **Baseline lookup, run with a stubbed gh:**
  - the artifact is on the second commit back: it's used;
- the artifact is missing a file and an `api@` release exists: it uses
the release;
  - every source fails: it exits 1 and leaves no partial files behind;
  - gh fails: it exits 1.
- **Changelog and RPC check, run with the real oasdiff v1.33.0 binary:**
  - a real spec diff gives an 8.8 KB Markdown changelog;
  - identical specs give "No changes detected";
- a missing baseline or a bad JSON spec gives a note, and every one of
these exits 0;
- `rpc-breaking` on identical schemas exits 0, and on a schema with a
removed method it prints `::error` and exits 1.
- No UI change.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>

<!-- junior-request-attribution:start -->
via @BYK.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:agent%3A69da19b385017e68b7f9eace56e9477f -->

--

[View Session in
Sentry](https://sentry.sentry.io/explore/conversations/agent%3A69da19b385017e68b7f9eace56e9477f/?project=4510944073809921)

<!-- junior-session-footer:end -->

---------

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
sentry-junior Bot and others added 2 commits November 14, 2026 00:59
Craft's github target publishes the release inside its own step, so with
github listed first, `release: published` fired before npm had the
version. Listing npm first means bump-sentry-docs.yml can dispatch right
away, so the wait-for-npm-version script goes away.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit cc36203. Configure here.

Comment thread .github/workflows/openapi.yml Resolved
sentry-junior Bot and others added 2 commits November 14, 2026 01:19
`make build-rpc-schema` writes .artifacts/rpc_method_schema.json, so the
api-schemas upload no longer found it at the repository root. Upload the
copy under static/packages/api/spec/ instead: upload-artifact skips
hidden directories such as .artifacts/.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
… path

The generated buildUrl appended API_BASE_PATH to baseUrl as is, so a base
URL with trailing slashes, such as a per-request one or one with more
than the single slash mergeConfigs strips, produced `//api/0`. The new
withApiBasePath helper trims them first and gives `/api/0` when no base
URL is set.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
With craft's auto changelog policy, a non-empty Unreleased section
replaces the notes craft generates from commits, so it would have been
the whole changelog for the first api@ release. The fix's commit is in
those generated notes already.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview — 94473dd5 Deployed Nov 14, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants