Scopes
The three-scope model for propagating contextual data through SDK execution — global, isolation, and current scopes.
Scopes are the mechanism by which Sentry SDKs propagate contextual data (tags, breadcrumbs, user, contexts, attributes) through an application and apply it to captured events. Every SDK MUST implement three scope types — global, isolation, and current — each with a different propagation lifetime.
This model replaces the Hub & Scope model. The Hub is removed; its functionality is absorbed into the three scope types. The design aligns with OpenTelemetry's Context propagation — an immutable, fork-on-write mechanism that carries execution-scoped values across API boundaries. See RFC 0122 for the original design.
Related specs:
- Attributes — attribute type system for scope data
- Breadcrumbs — structured event trail on scopes
- Hub & Scope — deprecated predecessor
| Scope | Lifetime | Storage | Who Forks It |
|---|---|---|---|
| Global | Entire process | Global variable | Never forked |
| Isolation | Request, tab, or user session | Context variable / thread-local / async-local | SDK integrations (automatically) |
| Current | Single span or withScope block | Context variable / thread-local / async-local | withScope() or starting a new span |
When a scope is forked, the new scope receives a copy of all data from the parent. Changes to the forked scope do not affect the parent, and changes to the parent after forking do not affect the fork.
This replaces the manual hub-cloning pattern from the old model. Users no longer need to manage scope isolation — it happens automatically when spans are created or withScope() is called.
SDKs and their integrations automatically populate scopes with useful contextual data such as tags, contexts, and attributes. This typically happens by hooking into a framework (e.g., a web framework middleware setting request-related context on the isolation scope). This automatic enrichment is a key benefit of scopes — users get useful data on events without manual instrumentation. See Data Scrubbing for considerations around sensitive data.
Clients are resolved by walking the scope chain: current scope -> isolation scope -> global scope. If no client is found, a NoOpClient is used (see Client Resolution).
The global scope is a process-wide singleton. Data set on the global scope MUST be applied to all events emitted by the SDK, regardless of thread, request, or execution context.
The global scope is typically used for application-wide data such as release and environment.
The global scope is never forked. There is exactly one global scope per process.
Before Sentry.init() is called, the global scope MUST exist with a NoOpClient. Sentry.init() replaces the NoOpClient with the configured client on the global scope.
The isolation scope MUST contain data specific to the current execution context: a single request (on a server), a single tab (in a browser), or a single user session (on mobile).
Top-level SDK APIs such as Sentry.setTag(), Sentry.setUser(), and Sentry.setContext() MUST write to the isolation scope.
The isolation scope SHOULD be implemented using a context variable, thread-local storage, async-local storage, or an equivalent mechanism appropriate for the platform.
SDK integrations MUST handle the forking of isolation scopes automatically. Users MUST NOT need to manage or think about isolation scope forking. SDKs SHOULD NOT fork isolation scopes more than necessary — excessive forking reintroduces the problems isolation scopes are designed to solve.
When the isolation scope is forked, the SDK MUST also fork the current scope at the same time. This avoids requiring users to call both withScope and withIsolationScope.
When to fork isolation scope:
SDKs SHOULD fork the isolation scope at natural isolation boundaries:
- Incoming HTTP request (server)
- New tab or navigation (browser)
- New user session (mobile)
- Queue job or background task
- In POTel SDKs,
Propagator.extractis a natural fork point (see OTel Context Alignment)
The current scope MUST maintain data for the active span. When a new span is started, the current scope of the parent span MUST be forked (duplicated), transferring all data from the parent scope to the new scope. This allows modifications specific to the new span without affecting the parent span.
Any changes made to the current scope after forking MUST NOT impact the parent scope. Any changes made to the parent scope after forking MUST NOT impact the forked scope.
The current scope SHOULD be implemented using a context variable, thread-local storage, async-local storage, or an equivalent mechanism appropriate for the platform.
Users MAY fork the current scope explicitly by invoking Sentry.withScope() or implicitly by starting a new span.
Data from all three scope types MUST be merged in a specific order before being applied to an event:
- Data from the global scope is...
- merged with data from the isolation scope, which is...
- merged with data from the current scope, which is...
- optionally merged with additional data (e.g., from
captureException(error, { tags }))... - applied to the event
When the same key exists in multiple scopes, the more specific scope wins: current > isolation > global. Event-level data (from step 4) takes highest precedence.
Scope forking implements copy-on-write:
withScope(callback)forks the current scope for the duration of the callback. The forked scope is discarded when the callback returns.- Starting a new span forks the current scope automatically.
withIsolationScope(callback)forks both the isolation scope and the current scope.
After forking, the original and forked scopes are fully independent. Mutations to either do not affect the other.
This replaces the old model where users had to manually create hub clones to achieve isolation in async contexts. The new model handles this automatically — when OTel or the SDK forks a context (e.g., for a new span), the scope is forked with it.
Users MUST be able to attach attributes to any scope. Attributes set on scopes follow scope precedence rules (current > isolation > global) and are applied to telemetry items at capture time. See the Attributes spec for the full data model, API, precedence rules, and examples. (since 1.13.0)
In addition to attributes and breadcrumbs, SDKs MUST allow users to set user context, string tags, structured contexts, severity level, grouping fingerprints, and event processor callbacks on any scope. SDKs MUST also provide a method to reset a scope to its defaults while keeping registered event processors.
These methods have the same semantics as in the Hub & Scope spec. The scope type determines the propagation lifetime of the data.
A client MUST always be available — SDKs MUST NOT return null from getClient().
Before Sentry.init() is called, a NoOpClient MUST be present on the global scope. Sentry.init() replaces it with the configured client.
Users MAY bind a different client to any of the three scopes. Client resolution walks the scope chain:
- Check the current scope for a client
- Check the isolation scope for a client
- Check the global scope for a client
- Fall back to the NoOpClient (should not happen if init was called)
The three-scope model is designed to align with OpenTelemetry's Context propagation. OTel Context is immutable — mutations create a new Context (fork). Sentry scopes mirror this behavior.
SDKs that use OTel for instrumentation (POTel) SHOULD store scopes on the OTel Context to leverage OTel's propagation mechanisms (thread propagation, async propagation, reactive library support).
SDKs SHOULD store both isolation and current scope on the OTel Context. This allows the SDK to rely on OTel's context propagation instead of maintaining its own propagation for each platform.
If the language allows modifying OTel spans (e.g., adding attributes or references), the SDK MAY store scope references directly on spans. Otherwise, a global weak-reference map from OTel spans to Sentry scopes SHOULD be used to avoid memory leaks.
SDKs need a hook to fork scopes when OTel creates new spans or contexts. Available hooks vary by language:
- Context forking: Intercept
context.with(...)calls - Context storing: Intercept
context.makeCurrent()calls - Span creation: Intercept new span creation in
SpanProcessor.onStart
SDKs SHOULD fork scopes when a new OTel span is created. If multiple hooks are available, SDKs MAY filter to only fork when the span changes, to reduce unnecessary forks.
SDKs SHOULD fork the isolation scope in Propagator.extract, which is called by auto-instrumentation for incoming server requests and message consumers. If this approach does not work for a given platform, the SDK MAY check span.isRemote to determine if a new isolation scope is needed.
In SpanProcessor.onEnd and SpanExporter.export, Context.current() returns the parent context, not the context that was active during span execution. This is by design in OTel. SDKs MUST retrieve the scope associated with a span via the span itself (if the language supports it) or via the global weak-reference map — not from Context.current().
The propagation context (trace ID, span ID, baggage/DSC) MUST be stored on the scope. It is used for:
- Generating
sentry-traceandbaggageheaders for outgoing requests - Connecting error events to the active trace (tracing without performance)
The isolation scope is the canonical location for the propagation context. When no active span exists, the propagation context from the isolation scope MUST be used. See Trace Propagation: Default Propagation for details.
During the migration from Hub & Scope to the three-scope model:
Top-level APIs (e.g.,
Sentry.setTag()) SHOULD write to both current and isolation scope during the migration phase. In a subsequent major version, they MUST write to isolation scope only.getCurrentHub()MAY be provided as a compatibility shim that proxies to the new scope APIs. It SHOULD be marked deprecated.configureScope(callback)SHOULD be deprecated. Users should migrate to callinggetIsolationScope()orgetCurrentScope()directly, depending on the intended scope lifetime.Hub-specific APIs (
pushScope,popScope,bindClient,run) SHOULD be shimmed to the new equivalents and marked deprecated.
See Hub & Scope: Migration for the complete API mapping table.
SDKs MUST expose the following top-level functions for scope access and manipulation:
| Function | Since | Description |
|---|---|---|
getGlobalScope() | 1.0.0 | Return the global scope singleton. |
getIsolationScope() | 1.0.0 | Return the isolation scope for the current execution context. |
getCurrentScope() | 1.0.0 | Return the current scope for the active span / execution context. |
getClient() | 1.0.0 | Return the client by walking the scope chain. Never returns null. |
| Function | Since | Description |
|---|---|---|
withScope(callback) | 1.0.0 | Fork the current scope, invoke callback, discard the fork when done. |
withIsolationScope(callback) | 1.0.0 | Fork both the isolation scope and current scope, invoke callback, discard the forks when done. |
These convenience functions write to the isolation scope:
| Function | Since | Target Scope |
|---|---|---|
Sentry.setTag(key, value) | 1.0.0 | Isolation scope |
Sentry.setUser(user) | 1.0.0 | Isolation scope |
Sentry.setContext(key, value) | 1.0.0 | Isolation scope |
Sentry.addBreadcrumb(breadcrumb) | 1.0.0 | Isolation scope |
Sentry.setAttributes(attributes) | 1.10.0 | Isolation scope |
Capture functions operate on the current scope:
| Function | Since | Description |
|---|---|---|
Sentry.captureException(error) | 1.0.0 | Capture an exception on the current scope. |
Sentry.captureMessage(message) | 1.0.0 | Capture a message on the current scope. |
Sentry.captureEvent(event) | 1.0.0 | Capture a raw event on the current scope. |
| Function | Since | Description |
|---|---|---|
Sentry.lastEventId() | 1.0.0 | Return the ID of the last event sent from the isolation scope. Useful for correlation, logging, and custom user feedback forms. |
These methods are available on any scope instance (see Behavior: Scope Data Methods for requirements):
| Method | Since | Description |
|---|---|---|
set_user(user) | 1.0.0 | Set user context (email, username, id, ip_address). |
set_tag(key, value) | 1.0.0 | Set a string tag for event searching. |
set_tags(tags) | 1.0.0 | Convenience for multiple set_tag calls. |
set_context(key, value) | 1.0.0 | Set structured context data. |
set_level(level) | 1.0.0 | Override event severity level. |
set_fingerprint(fingerprint[]) | 1.0.0 | Set grouping fingerprint. |
add_event_processor(fn) | 1.0.0 | Register an event processor callback. |
set_last_event_id(id) | 1.0.0 | Store the ID of the last event sent. Typically set on the isolation scope by the SDK after sending an event. |
last_event_id() | 1.0.0 | Return the ID of the last event sent. Typically read from the isolation scope. |
clear() | 1.0.0 | Reset scope to defaults, keeping event processors. |
// Global scope — applied to everything
Sentry.getGlobalScope().setAttributes({
"app.version": "2.1.0",
});
// Isolation scope — per-request (automatic in server frameworks)
// Top-level APIs write here
Sentry.setTag("transaction", "GET /users");
Sentry.setUser({ id: "user-123" });
// Current scope — localized data
Sentry.withScope((scope) => {
scope.setTag("operation", "db-query");
scope.setContext("query", { sql: "SELECT * FROM users" });
Sentry.captureException(error);
});
// scope is discarded here, "operation" tag does not leak
import sentry_sdk
# Global scope
sentry_sdk.get_global_scope().set_attributes({
"app.version": "2.1.0",
})
# Isolation scope (automatic per-request in WSGI/ASGI)
sentry_sdk.set_tag("transaction", "GET /users")
sentry_sdk.set_user({"id": "user-123"})
# Current scope — localized
with sentry_sdk.new_scope() as scope:
scope.set_tag("operation", "db-query")
sentry_sdk.capture_exception(error)
The diagram shows how withScope() and withIsolationScope() fork scopes and how data flows between them.
| Version | Date | Summary |
|---|---|---|
1.15.0 | 2026-02-03T00:00:00.000Z | Clarified array and unit support for scope attributes |
1.14.0 | 2025-12-19T00:00:00.000Z | Clarified current scope attribute application wording |
1.13.0 | 2025-11-25T00:00:00.000Z | Extracted dedicated attributes spec, scopes reference it |
1.12.0 | 2025-11-18T00:00:00.000Z | Removed explicit type from user-facing attribute API, SDKs SHOULD infer type |
1.11.0 | 2025-11-11T00:00:00.000Z | Added scope attribute precedence rules, removeAttribute method, telemetry-level precedence |
1.10.0 | 2025-11-03T00:00:00.000Z | Added global attributes on scopes (setAttributes, setAttribute) |
1.9.0 | 2025-10-17T00:00:00.000Z | Added scope inheritance/merging diagram |
1.8.0 | 2024-11-26T00:00:00.000Z | Formalized as normative spec with RFC 2119 keywords |
1.7.0 | 2024-11-05T00:00:00.000Z | Added scope forking diagrams |
1.6.0 | 2024-08-05T00:00:00.000Z | Python scope API examples |
1.5.0 | 2024-04-03T00:00:00.000Z | Old-to-new API mapping table |
1.4.0 | 2024-03-19T00:00:00.000Z | POTel integration — OTel Context storage, hooks, span-to-scope mapping |
1.3.0 | 2024-03-11T00:00:00.000Z | Clarified copy-on-write semantics |
1.2.0 | 2024-03-08T00:00:00.000Z | Rationale for OTel alignment, backwards compatibility strategy |
1.1.0 | 2024-03-01T00:00:00.000Z | Detailed scope descriptions, forking behavior |
1.0.0 | 2024-02-14T00:00:00.000Z | Initial spec — three-scope model (global, isolation, current), RFC 0122 |
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").