Description
Scope.Clone() copies data into the new scope through the public mutators, so cloning has side effects whenever scope sync is enabled (Android, iOS/Mac Catalyst, desktop Native AOT).
Clone() creates a new Scope and calls Apply(clone), which:
Only attachments are copied directly, to avoid triggering a scope sync.
Hub clones the current scope on every CaptureEvent(..., configureScope) and CaptureFeedback(..., configureScope) call (1, 2). In non-global mode, PushScope also clones.
Impact
- Duplicate breadcrumbs in native crash reports. On MAUI, every
SentrySdk.CaptureException(ex, scope => ...) re-sends all existing breadcrumbs to the native SDK.
- Tags from a per-event callback persist on the native scope. Tags, user or environment set inside the
configureScope callback are meant for that one event, but reach the native scope through the observer and stay there. They then appear on later native crashes.
BeforeBreadcrumb runs again on breadcrumbs it has already processed, on every clone. A callback that mutates breadcrumbs mutates them twice.
- Native AOT servers re-sync the whole scope per request. In non-global mode with scope sync (e.g. an ASP.NET Core app published as Native AOT), every
PushScope re-syncs the whole scope.
Expected
Cloning a scope should produce an equivalent scope without calling the scope observer, BeforeBreadcrumb or TagFilters.
It's an open question whether mutations made on a temporary scope from a configureScope callback should sync to native at all. Since that scope is only used for a single event, probably not.
Notes
Found while researching #5197.
Description
Scope.Clone()copies data into the new scope through the public mutators, so cloning has side effects whenever scope sync is enabled (Android, iOS/Mac Catalyst, desktop Native AOT).Clone()creates a newScopeand callsApply(clone), which:other.AddBreadcrumb(breadcrumb)for every breadcrumb. That re-runsBeforeBreadcrumband callsScopeObserver.AddBreadcrumbagainother.SetTag(key, value), which re-appliesTagFiltersand callsScopeObserver.SetTagUser.CopyTo(other.User), which firesScopeObserver.SetUseronce per propertyother.Environment ??= Environment, which callsScopeObserver.SetEnvironmentOnly attachments are copied directly, to avoid triggering a scope sync.
Hubclones the current scope on everyCaptureEvent(..., configureScope)andCaptureFeedback(..., configureScope)call (1, 2). In non-global mode,PushScopealso clones.Impact
SentrySdk.CaptureException(ex, scope => ...)re-sends all existing breadcrumbs to the native SDK.configureScopecallback are meant for that one event, but reach the native scope through the observer and stay there. They then appear on later native crashes.BeforeBreadcrumbruns again on breadcrumbs it has already processed, on every clone. A callback that mutates breadcrumbs mutates them twice.PushScopere-syncs the whole scope.Expected
Cloning a scope should produce an equivalent scope without calling the scope observer,
BeforeBreadcrumborTagFilters.It's an open question whether mutations made on a temporary scope from a
configureScopecallback should sync to native at all. Since that scope is only used for a single event, probably not.Notes
Found while researching #5197.