Skip to content

Rails.error.set_context data is silently lost — Sentry captures before the Rails ErrorSubscriber runs #2931

@clinejj

Description

@clinejj

Issue Description

When using Rails.error.set_context to attach debugging context before raising an exception, the context never appears in Sentry events. This happens because Sentry's own exception capture (via CaptureExceptions middleware for web requests, or ActiveJobExtensions::SentryReporter for jobs) always runs before the exception reaches Rails.error.report, and Sentry's @__sentry_captured deduplication then discards the second capture that would have carried the context.

This makes Rails.error.set_context effectively useless for anyone using register_error_subscriber = true — the subscriber receives the context correctly, but by the time it calls capture_exception, the exception is already marked as captured and gets silently skipped.

Workaround

We are using a global event processor in our Sentry initializer that reads ActiveSupport::ExecutionContext directly in the calling thread before Sentry dispatches to its background worker:

Sentry::Scope.add_global_event_processor do |event, _hint|
  rails_context = ActiveSupport::ExecutionContext.to_h
  if rails_context.present?
    safe_context = rails_context.transform_values do |value|
      case value
      when String, Numeric, Symbol, NilClass, TrueClass, FalseClass
        value
      else
        value.class.name
      end
    end
    event.contexts["rails.error"] = (event.contexts["rails.error"] || {}).merge(safe_context)
  end
  event
end

This works because event processors run synchronously in the calling thread (where ExecutionContext is still populated) before the event is handed off to Sentry's background worker. We filter non-primitive values to avoid leaking sensitive data from objects like ActiveRecord models.

Reproduction Steps

  1. Configure Sentry with config.rails.register_error_subscriber = true

  2. In any controller action or ActiveJob, set context and raise:

Rails.error.set_context(debug_key: "important_value")
raise StandardError, "something went wrong"
  1. Check the resulting Sentry event — the debug_key context is missing.

Root cause — two paths, both broken

ActiveJob path: SentryReporter.record wraps perform_now with rescue Exception => e and calls capture_exception directly. ActiveJob's execution chain (ActiveJob::Execution#_perform_job) has no Rails.error.record wrapper, so Rails.error.report is never called. The ErrorSubscriber never runs at all.

Web request path (production): The exception propagates upward through:

  1. RescuedExceptionInterceptor → re-raises
  2. DebugExceptions → logs, re-raises (production, show_detailed_exceptions is false)
  3. CaptureExceptions → catches, calls Sentry::Rails.capture_exception (sets @__sentry_captured = true), re-raises
  4. ShowExceptions → catches, renders error page, sets env["action_dispatch.report_exception"]
  5. Executor → sees report_exception, calls Rails.error.report(error) → merges ActiveSupport::ExecutionContext.to_h (which has the set_context data) → dispatches to ErrorSubscriber
  6. ErrorSubscriber#report → calls Sentry::Rails.capture_exceptionHub#capture_exception checks Sentry.exception_captured?(exception)truereturns immediately, discarding the context

So for web requests, the context does reach the ErrorSubscriber, but the event is deduplicated away because CaptureExceptions already captured it in step 3.

Expected Behavior

Context set via Rails.error.set_context (stored in ActiveSupport::ExecutionContext) should appear in the Sentry event, either by:

  • Having CaptureExceptions and SentryReporter read ActiveSupport::ExecutionContext.to_h at capture time (e.g., merge into contexts["rails.error"]), or
  • Reversing the capture priority so the ErrorSubscriber path (which includes the context) is preferred, and CaptureExceptions/SentryReporter defer to it, or
  • Some other approach that ensures the two systems don't silently discard each other's data

Actual Behavior

Context set via Rails.error.set_context never appears in Sentry events. Users expect that with register_error_subscriber = true, context set through the Rails error reporter API would be forwarded to Sentry. Instead, it is silently discarded in all cases.

Ruby Version

4.0.2

SDK Version

6.5.0

Integration and Its Version

Rails 8.1.3 (ActiveJob via GoodJob)

Sentry Config

Sentry.init do |config|
  config.dsn = "..."
  config.breadcrumbs_logger = [:active_support_logger, :http_logger]
  config.enabled_patches << :faraday
  config.rails.register_error_subscriber = true
  config.traces_sampler = lambda { |ctx| ... } # custom sampling
end

Metadata

Metadata

Assignees

No one assigned
    No fields configured for issues without a type.

    Projects

    Status

    Waiting for: Product Owner

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions