Skip to content

fix(rails): ensure Rails.error.set_context is not lost#3024

Open
solnic wants to merge 4 commits into
masterfrom
2931-railserrorset_context-data-is-silently-lost-sentry-captures-before-the-rails-errorsubscriber-runs
Open

fix(rails): ensure Rails.error.set_context is not lost#3024
solnic wants to merge 4 commits into
masterfrom
2931-railserrorset_context-data-is-silently-lost-sentry-captures-before-the-rails-errorsubscriber-runs

Conversation

@solnic

@solnic solnic commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Use ActiveSupport::ExecutionContext to maintain in the context what's set via Rails.error.set_context.


Closes #2931

@solnic
solnic force-pushed the 2931-railserrorset_context-data-is-silently-lost-sentry-captures-before-the-rails-errorsubscriber-runs branch 3 times, most recently from 0b442b1 to c29006f Compare July 17, 2026 08:46
@solnic
solnic marked this pull request as ready for review July 17, 2026 08:48
Comment thread sentry-rails/lib/sentry/rails/structure_sanitizer.rb
Comment thread sentry-rails/lib/sentry/rails/error_reporter_context.rb
@solnic
solnic requested review from dingsdax and sl0thentr0py July 17, 2026 09:27

@dingsdax dingsdax left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

module Sentry
module Rails
module StructureSanitizer
def self.sanitize(value, &leaf)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this need to take a block

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sl0thentr0py because ErrorReporterContext uses it for primitive-based filtering:

            sanitized = Sentry::Rails::StructureSanitizer.sanitize(execution_context) do |leaf|
              PRIMITIVE_CLASSES.any? { |klass| leaf.is_a?(klass) } ? leaf : leaf.class.name
            end

@solnic
solnic force-pushed the 2931-railserrorset_context-data-is-silently-lost-sentry-captures-before-the-rails-errorsubscriber-runs branch from c29006f to 69d3b44 Compare July 17, 2026 10:45
Comment thread sentry-rails/lib/sentry/rails/structure_sanitizer.rb
Comment thread sentry-rails/lib/sentry/rails/structure_sanitizer.rb
end

{ "rails.error" => sanitized }
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tags and hint context ignored

Medium Severity

ErrorReporterContext.contexts dumps the full ExecutionContext into rails.error, but unlike ErrorSubscriber it never lifts tags or hint out of that hash. For unhandled exceptions and ActiveJob failures, CaptureExceptions/ActiveJob usually win the capture race, so Rails.error.set_context(tags: ...) / hint: ... still never become real Sentry tags or hints.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 22f13b0. Configure here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's material for a follow-up PR.

@solnic
solnic force-pushed the 2931-railserrorset_context-data-is-silently-lost-sentry-captures-before-the-rails-errorsubscriber-runs branch from 22f13b0 to bb0629b Compare July 20, 2026 12:54

sanitized = Sentry::Rails::StructureSanitizer.sanitize(execution_context) do |leaf|
PRIMITIVE_CLASSES.any? { |klass| leaf.is_a?(klass) } ? leaf : leaf.class.name
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timestamps reduced to class names

Medium Severity

PRIMITIVE_CLASSES keeps Symbol because Rails JSON can encode it, but common context values like Time, ActiveSupport::TimeWithZone, and Date are replaced with their class name. That drops useful Rails.error.set_context data on the CaptureExceptions and ActiveJob paths, while ErrorSubscriber still preserves those values.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit bb0629b. Configure here.

Comment thread sentry-rails/lib/sentry/rails/error_reporter_context.rb
Comment on lines +25 to +34
def self.sanitize_range(range, &leaf)
return range.to_s if range.begin.nil? || range.end.nil?

boundary = range.begin

if boundary.is_a?(::ActiveSupport::TimeWithZone)
range.to_s
else
range.map { |v| sanitize(v, &leaf) }
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The sanitize_range method can raise a TypeError for ranges with mixed types (e.g., 1..Time.current), which crashes the exception capturing process because it's unhandled.
Severity: HIGH

Suggested Fix

Update the condition in sanitize_range to check if either range.begin or range.end is an ActiveSupport::TimeWithZone before attempting to iterate. This will correctly convert such ranges to strings instead of causing a TypeError.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: sentry-rails/lib/sentry/rails/structure_sanitizer.rb#L25-L34

Potential issue: The `sanitize_range` method was refactored to only check if
`range.begin` is an `ActiveSupport::TimeWithZone`. If an ActiveJob fails and its
arguments contain a range with a non-time start and a time end (e.g.,
`1..Time.current`), the sanitization logic will attempt to call `.map` on this
non-iterable range, raising a `TypeError`. Because the call to `sentry_context` within
`capture_exception` is not wrapped in an error handler, this `TypeError` will propagate,
preventing the original exception from being reported to Sentry and potentially crashing
the job worker.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d26b269. Configure here.

{ "rails.error" => sanitized }
rescue StandardError => e
Sentry.sdk_logger&.error("sentry-rails: failed to sanitize Rails.error execution context: #{e.class}: #{e.message}\n #{Array(e.backtrace).first(5).join("\n ")}")
{}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rescue misses stack overflow failures

Medium Severity

contexts rescues only StandardError, but recursive sanitization of cyclic or deeply nested ExecutionContext values raises SystemStackError, which subclasses Exception. That escapes the handler, so capture_exception never runs and the middleware can surface the stack error instead of the original exception—contrary to the “still report when context can’t be sanitized” guarantee.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d26b269. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

3 participants