Skip to content

feat(toolkit-core): Let a request scope bypass cached reads - #1720

Merged
BYK merged 1 commit into
mainfrom
feat/toolkit-core-fresh-reads
Oct 29, 2026
Merged

BYK merged 1 commit into
mainfrom
feat/toolkit-core-fresh-reads

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

This adds a request-scoped fresh-read bypass to the shared API transport in @sentry/toolkit-core/api-transport. It is task 5.3 of #1669 (unify-sentry-api-layer) and builds on the shared transport from #1719.

What's shared

createApiTransport accepts a new optional fresh?(): boolean hook. While the hook returns true:

  • GETs skip the cache lookup and go to the network.
  • The response is still stored, so it replaces the cached entry and later reads see the new data.

A host backs the hook with request-scoped state, for example AsyncLocalStorage around a single MCP tool call that asked for fresh: true. That keeps the bypass to one call rather than the whole process. This is the transport side of the MCP's fresh input planned in 6.3.

Behavior changes

None for current callers. The hook is optional, and no host sets it yet. The CLI's --fresh flag keeps its existing process-wide bypass in cli/src/lib/response-cache.ts, with reads skipped and writes kept as before. This PR leaves that unchanged.

Relation to #1669

Verification

  • New transport test skips the cache in a fresh scope and replaces the entry. It stores v1, then reads in a fresh scope and gets v2 from the network. Once the fresh scope ends, the next read is a cache hit for v2.
  • toolkit-core: 21 files and 270 tests pass.
  • CLI request, cache and API tests (test/lib/sentry-client*, test/lib/response-cache*, test/lib/api): 49 files and 1,049 tests pass.
  • tsc --noEmit passes for toolkit-core and cli.
  • oxlint, oxfmt --check and ast-grep scan are clean on the changed files.

via BYK.

--

View Junior Session in Sentry

Add a `fresh` hook to the API transport. While it returns true, GETs skip
the cache lookup and go to the network, and the response still replaces
the cached entry, so later reads see it. Hosts back the hook with
request-scoped state, such as AsyncLocalStorage around a tool call.

Implements #1669 task 5.3.

Co-Authored-By: BYK <burak.kaya@sentry.io>
@vercel

vercel Bot commented Oct 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
sentry-local Skipped Skipped Oct 29, 2026 12:17pm UTC

Request Review

sentry-junior Bot added a commit that referenced this pull request Oct 29, 2026
5.3 is in review in #1720, and 6.1 is stacked on it.

Co-Authored-By: BYK <burak.kaya@sentry.io>
@github-actions github-actions Bot added the risk: medium PR risk score: medium label Oct 29, 2026
@BYK
BYK enabled auto-merge (squash) October 29, 2026 12:18
@BYK
BYK merged commit 6fe02ce into main Oct 29, 2026
32 checks passed
@BYK
BYK deleted the feat/toolkit-core-fresh-reads branch October 29, 2026 12:33
BYK added a commit that referenced this pull request Oct 29, 2026
This PR sends every MCP request to the Sentry API through the shared
transport, and moves retries out of `SentryApiService` into it. It is
task **6.1** of #1669 (`unify-sentry-api-layer`) and builds on the
shared transport (#1719) and the fresh-read hook (#1720).

### What changed

- **One transport:** `SentryApiService` sends every request through
`createApiTransport` from `@sentry/toolkit-core/api-transport`, using
the MCP's existing `REQUEST_RETRY_POLICY`. That includes `@sentry/api`
operations, which already go through `request()`.
- **One retry layer:** `SentryApiService` no longer has its own retry
loop or network-failure bookkeeping. The shared transport retries a 429
for any method, and a 500/502/503/504 or a network failure for GET and
HEAD. `SentryApiService` still parses the final error response, so
`ApiRateLimitError.retryAfterMs` is still read from `Retry-After` or
`X-Sentry-Rate-Limit-Reset`.
- **Transport options:** `credentials` and `timeoutMs` on
`createApiTransport` are now optional. Without a `CredentialProvider`,
the transport keeps the `Authorization` header the request already has,
never refreshes, and skips the cache, because cache entries are tied to
a credential. The MCP uses it this way, and still sets no timeout, as
before.
- **Abort reason:** an abort while a request is in flight now rejects
with the caller's `signal.reason`, instead of being treated as a
retryable network failure.

An earlier revision also added a host-supplied `fetch` option to
`McpServerOptions` and `ServerContext`. That is gone: per the revised
#1669 plan, the shared API layer moves into toolkit-core and then
`@sentry/api`, and the CLI passing its fetch to `sentry mcp` is
deferred.

### Intentional behavior changes

- **`allowStatuses`:** a status a caller lists there is now returned
only after the transport has finished retrying it. Before, it came back
on the first attempt. Today's callers only list 404 and 400, which are
never retried, so they see no difference.
- **Abort during a retry wait:** if the caller's signal aborts while the
transport is waiting to retry, the abort error is rethrown as-is. It is
no longer wrapped as a connection failure.

### Relation to #1669

- Done: 5.0 (#1673, #1710), 5.1 and 5.2 (#1719), 5.3 (#1720).
- This PR: 6.1.
- Next: consolidate the MCP's `SentryApiService` and the CLI's API
modules into one browser-safe API client in toolkit-core, stacked on
this PR.

### Verification

- New `mcp-core` tests:
- A GET that is rate-limited on every attempt reaches the API at most
`maxRetries + 1` times.
- An in-flight abort rejects with the caller's reason, after one
attempt.
- New `toolkit-core` tests:
  - Without credentials, the request's own `Authorization` is kept.
  - Without credentials, the cache is never consulted.
  - An in-flight abort keeps the caller's reason.
- Suites:

  | Package | Result |
  | --- | --- |
  | `toolkit-core` | 273 pass |
  | `mcp-core` | 1,899 pass, 6 skipped |
  | `mcp-server` | 87 pass |
  | `mcp-cloudflare` | 443 pass |

- `tsc --noEmit` passes for `toolkit-core`, `mcp-core`, `mcp-server`,
`mcp-cloudflare`, and `cli`. `oxlint` and `oxfmt --check` are clean on
the changed files.
- Test changes:
- Hand-built response mocks in `client.test.ts` gained a `status`,
because the transport reads it to decide on a retry.
- Header assertions in `client.test.ts` and `mcp-handler.test.ts` now
read through `Headers`, because the transport passes a `Headers` object.

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

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

--

[View Junior Session in
Sentry](https://sentry.sentry.io/explore/conversations/agent%3Aa6eddc0161cff61914454da9f74fcdf1/?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 branch was previously deployed

1 inactive deployment
Preview — 2709634f Deployed Oct 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant