Repository navigation
feat(apidocs): Mark idempotent operations with x-sentry-idempotent - #128930
Merged
Merged
Conversation
Endpoints can list non-safe methods in `idempotent_methods` when resending the identical request after a timeout or 5xx cannot change the outcome. The schema hooks stamp `"x-sentry-idempotent": true` on those operations so generated clients such as `@sentry/api` can retry them instead of hard-coding per-call opt-ins. The build fails if an endpoint lists a safe method (RFC 9110 9.2.1) or a method it does not declare in `publish_status`. Declare it for chunk upload and the DIF and artifact bundle assemble endpoints: blobs and assemble state are keyed by checksum, so a resend reports the existing state. Preprod assemble is left out because a resend after the artifact leaves UPLOADING creates a second artifact. Refs #128927 Co-Authored-By: BYK <burak.kaya@sentry.io>
BYK
approved these changes
Oct 27, 2026
BYK
marked this pull request as ready for review
October 27, 2026 21:10
wedamija
approved these changes
Oct 27, 2026
BYK
added a commit
to getsentry/sentry-api-schema
that referenced
this pull request
Oct 27, 2026
Adds `idempotent: boolean` to every `ApiOperation` in `@sentry/api/operations`. Retrying clients such as the CLI and MCP can then decide from the catalog whether to resend a request after a timeout or 5xx, instead of hard-coding that per call site. Refs getsentry/sentry#128927. The Sentry side, getsentry/sentry#128930, now stamps `x-sentry-idempotent: true` on operations whose endpoint declares the method in `idempotent_methods`. **Semantics** - `GET`, `HEAD`, `OPTIONS`, `TRACE`: always `true` (RFC 9110 §9.2.1). Clients get one field to check, with no method special-casing. - Other methods: `true` only when the operation has `x-sentry-idempotent: true`. `PUT` and `DELETE` are not assumed idempotent because not every Sentry handler follows RFC 9110 §9.2.2. Sentry makes the same choice. - `false` means "not known to be idempotent", not "known to be unsafe". It is documented that way in the type docs and the README. **Current effect** Today's `openapi-derefed.json` has no `x-sentry-idempotent` markers. The endpoints declared so far (chunk upload, DIF assemble, artifact bundle assemble) are all `PRIVATE`, so nothing non-safe is marked yet. In the current build, 147 of 266 operations are `idempotent: true`, all safe methods. Non-safe operations will be marked as they're published with the declaration. **Verification** - `bun run build`, `bun run typecheck`, `bun test` (167 pass), `bun run test:node` - New test: a marked `POST` is `true`; an unmarked `POST`/`PUT`/`DELETE` is `false`. Existing catalog fixtures assert `true` for `GET` and `false` for an unmarked `POST`. <!-- junior-request-attribution:start --> via **BYK**. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:local%3Aweb%3A828b4ad5f18c5305232eec52 --> -- [View Junior Session in Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3A828b4ad5f18c5305232eec52/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <burak.kaya@sentry.io>
BYK
added a commit
that referenced
this pull request
Oct 28, 2026
…ental (#128999) Follow-up to #128930. Publishes the endpoints that declare `idempotent_methods` so the `x-sentry-idempotent` markers actually reach the public schema, and through it `@sentry/api` (getsentry/sentry-api-schema#111 already reads the extension). Refs #128927. **What changes** All four operations are `PUBLIC_EXPERIMENTAL` and fully documented: - `GET /organizations/{org}/chunk-upload/` (`getOrganizationChunkUploadOptions`): the upload URL and limits. Clients need it before they upload, so it's published with the POST. - `POST /organizations/{org}/chunk-upload/` (`uploadOrganizationChunks`): multipart body described by a docs-only `ChunkUploadSerializer`, following the `ReleaseFileUploadSerializer` precedent. - `POST /projects/{org}/{project}/files/difs/assemble/` (`assembleProjectDebugFiles`) - `POST /organizations/{org}/artifactbundle/assemble/` (`assembleOrganizationArtifactBundle`) The three POSTs carry `x-sentry-idempotent: true`. GET doesn't, because safe methods are implicit. **Notes for review** - **One schema for validation and docs.** The assemble endpoints' jsonschema moved to module constants that both validation and `extend_schema` use, so the docs can't drift from what's enforced. Validation behavior is unchanged. - **DIF body is a map keyed by checksum.** OpenAPI 3.0 has no `patternProperties`, so the docs version uses `additionalProperties`, and the checksum key format is explained in the description. `_validate_request_body` raised `KeyError: 'properties'` on map bodies. It now checks field descriptions under the map's `additionalProperties`. The new `MapRequestBodyTest` covers this. - **Types.** Added response TypedDicts and typed `batch_assemble`. `state` is `str`, not a `Literal`, because `ChunkFileState` values are plain strings. - **`accept` is now a list.** It was a tuple, which spectacular documents as a fixed-length array, so the GET options response returns `list(CHUNK_UPLOAD_ACCEPT)` instead. The JSON output is the same. - **File parts show as `format: uri`.** That's how spectacular renders `FileField`, and it matches the existing release-file upload docs. Happy to override it to `binary` if reviewers prefer. **Verification** - `sentry django spectacular --validate --fail-on-warn` passes. The deref build and `openapi-examples-validator --no-additional-properties` pass with no errors. - `ruff check`, `ruff format --check`, `flake8`, and `mypy` are clean on the changed files. - Passing: `tests/apidocs/test_hooks.py`, `test_operation_id_uniqueness.py`, `test_chunk_upload.py`, `test_organization_artifactbundle_assemble.py` (78 tests). - `test_dif_assemble.py` needs an objectstore server, which my sandbox doesn't have, so CI is the authoritative run there. I checked `parse_assemble_request_payload` directly instead: it accepts valid payloads and rejects bad checksum keys, a missing `name`, non-hex chunks, and invalid `debug_id`, the same as before. <!-- junior-request-attribution:start --> via **BYK**. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:slack%3AC08J1NSPU6S%3A1792772794.175999 --> -- [View Junior Session](https://junior-prod.sentry.dev/conversations/slack%3AC08J1NSPU6S%3A1792772794.175999) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC08J1NSPU6S%3A1792772794.175999/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <burak.kaya@sentry.io>
This was referenced Oct 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lets endpoints declare which non-safe methods are idempotent, and publishes that as an OpenAPI operation extension. Generated clients such as
@sentry/api, and through it the CLI and MCP, can then retry those operations on timeouts and 5xx responses instead of hard-coding per-call opt-ins.Refs #128927. Context: getsentry/toolkit#1677.
What changes
Endpoint.idempotent_methods: frozenset[HTTP_METHOD_NAME](default empty). Listing a method there means resending the identical request after a timeout or 5xx cannot change the outcome.custom_preprocessing_hookcollects declarations for every endpoint, like silos, and resets them on every build.custom_postprocessing_hookstamps"x-sentry-idempotent": trueon matching operations. Operations without a declaration get no marker.GET/HEAD/OPTIONS/TRACE, RFC 9110 §9.2.1) or a method it does not declare inpublish_status.PUT/DELETEare not marked automatically. Not every handler lives up to RFC 9110 §9.2.2, so the absence of the marker means "not known to be idempotent".src/sentry/apidocs/README.md.Declared on
ChunkUploadEndpointPOST: blobs are unique per checksum.DifAssembleEndpointPOSTandOrganizationArtifactBundleAssembleEndpointPOST: assemble state is cached per checksum, so a resend reports it instead of starting a second job.ProjectPreprodArtifactAssembleEndpointis deliberately not marked.create_preprod_artifactonly reuses a row while it is stillUPLOADING, so a resend after that creates a second artifact and enqueues another assemble task.Open questions for review
PRIVATE, so the public schema doesn't change yet. The declarations are inert until those endpoints are published, or until@sentry/apibuilds from a schema that includes them. Which route do we want?x-sentry-idempotent, or a richerx-sentry-retryobject?Verification
ruff checkandruff format --checkare clean on the changed files.IdempotentOperationsTestcases intests/apidocs/test_hooks.pycover: marking only declared methods, collecting unpublished methods, resetting between builds, and both validation errors.hooks.pywith stubbed imports, and they pass. CI is the authoritative run.via BYK.
--
View Junior Session [Sentry]