Skip to content

ErrorSubscriber double-captures ActiveJob exceptions already captured by ActiveJobExtensions #217

Description

@jeffpeterson

Bug description

When both auto_capture_exceptions and auto_instrument_active_job are enabled, every unhandled ActiveJob exception is captured twice — once by ActiveJobExtensions and once by ErrorSubscriber.

ActiveJobExtensions#perform_now rescues the error, calls PostHog.capture_exception with $exception_source: 'active_job', and re-raises. The re-raised error then reaches the Rails error reporter (ActiveJob's executor reports it with source application.active_support), and ErrorSubscriber#report captures it again.

ErrorSubscriber already has a guard for exactly this class of problem on the web path:

# lib/posthog/rails/error_subscriber.rb
# Skip if in a web request - CaptureExceptions middleware will handle it
# with richer context (URL, params, controller, etc.)
return if PostHog::Rails.in_web_request?

…but there is no equivalent guard for the ActiveJob path, and no dedupe key ties the two captures together, so both events land in error tracking.

Observed

In our project the split is exact — every job failure produces one event of each source:

$exception_source count (30d)
active_job 506
application.active_support 506

The active_job events carry the richer context ($job_class, $job_id, $job_arguments, …); the application.active_support duplicates carry none of it. Besides doubling event volume, the duplicates skew issue occurrence counts in error tracking.

How to reproduce

  1. Rails 8.1 app, posthog-rails 3.16.0, with:
    PostHog::Rails.configure do |config|
      config.auto_capture_exceptions = true
      config.auto_instrument_active_job = true
    end
  2. Enqueue any job that raises, e.g. raise ArgumentError in perform.
  3. Two $exception events are captured for the single failure: $exception_source: 'active_job' and $exception_source: 'application.active_support'.

Expected

One captured exception per job failure — presumably the ActiveJobExtensions one, since it has the job context.

Suggested fix

Mirror the web-request guard in ErrorSubscriber#report: skip sources that ActiveJobExtensions already covers (e.g. skip when the reported source is ActiveJob's executor and auto_instrument_active_job is enabled), or set a thread/execution-local flag in capture_job_exception and check it in ErrorSubscriber, the same way in_web_request? works.

Happy to open a PR if you'd like — the thread-local approach looks like a small change.

Environment

  • posthog-ruby / posthog-rails 3.16.0
  • Rails 8.1.3, Ruby 3.3.8
  • Jobs running inline/async in a standard Rails app (observed with Turbo::Streams::BroadcastStreamJob among others)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions