Skip to content

feat(rails): carry request context into active job - #308

Merged
pauldambra merged 5 commits into
posthog/rails-span-contextfrom
posthog/active-job-context
Oct 26, 2026
Merged

pauldambra merged 5 commits into
posthog/rails-span-contextfrom
posthog/active-job-context

Conversation

@pauldambra

@pauldambra pauldambra commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

[Robot] Prepared with PostHog Desktop on behalf of @pauldambra.

💡 Motivation and Context

Rails users need background job events and spans to keep the session and person IDs from the request that queued the job. Current automatic job instrumentation captures exceptions but loses those IDs across the queue.

  • Use the existing auto_instrument_active_job option to copy only session and distinct IDs into the job payload. Request properties are excluded.
  • Restore a fresh job scope after argument deserialization. The existing job identity extractor takes precedence over the saved browser identity.
  • Keep the snapshot across retries and restore the surrounding context after each attempt. Old jobs without saved IDs run with empty context.
  • Retain one exception capture path, so handled retry and discard errors do not produce extra exception events.

Events, optional logs, and child spans use the same job context. An existing job span receives IDs only when its public Active Job markers match the current job. Other caller spans remain unchanged. No public option, runtime dependency, trace provider, or W3C trace transfer is added.

The scope covers the perform callback and final unhandled exception capture. Custom retry and discard handlers run outside that callback and do not receive its IDs. Bulk enqueue snapshots IDs when the adapter serializes the job.

Builds on #306. Tracks sdk-specs#128.

💚 How did you test it?

  • The OpenTelemetry bundle passes all 1,307 examples. The default bundle has no failures; its 26 optional cases pass in the OpenTelemetry bundle.
  • Real Active Job tests cover JSON queue payloads, delayed serialization, direct execution, bulk enqueue, retries, discarded errors, identity precedence, concurrent workers, and cleanup.
  • A real Rails app sends a request through the installed middleware, queues a job, and executes its serialized payload outside the request.
  • Span and log export checks preserve explicit values and link the active job IDs. No production ingestion or replay test was run.
  • The official OpenTelemetry Active Job instrumentation verifies two jobs, their child spans, an unchanged caller span, and cleanup.
  • The public API snapshot stays unchanged. Full RuboCop, formatting, lock checks, and normal commit hooks pass.

Verified with Rails and Active Job 7.2.3.1 and OpenTelemetry Active Job instrumentation 0.13.0. A full Rails version and queue adapter matrix was not run.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Models: GPT-6 (Codex) implemented the job context. GPT-6 Astra reviewed it and implemented the caller-span fix. Used PostHog Desktop. Used Ruby tools, GitHub CLI, and a synthetic Rails app. The implementation reuses the shared context and the existing job instrumentation option. Human review is required.


Created with PostHog Desktop

@pauldambra
pauldambra added this pull request to stack #309 October 17, 2026 20:40
@pauldambra pauldambra self-assigned this Oct 17, 2026
@pauldambra pauldambra changed the title posthog/active job context feat(rails): carry request context into active job Oct 17, 2026

@posthog posthog Bot 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.

PostHog Review · Standard · reviewed 0be7d06: 2 Should fix.

This is the only automatic review while this pull request is a draft. Comment @posthog review to review new changes sooner. Otherwise, a review of the new changes runs when you mark it ready for review.

Comment thread posthog-rails/lib/posthog/rails/active_job.rb Outdated
Comment thread posthog-rails/lib/posthog/rails/active_job.rb Outdated
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

posthog-ruby-async Compliance Report

Date: 2026-10-26T11:43:56.105385+00:00
Duration: 98471ms

⚠️ Some Tests Failed

46/47 tests passed, 1 failed


Capture Tests

✅ 30/30 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 108ms
Format Validation.Event Has Uuid ✅ 106ms
Format Validation.Event Has Lib Properties ✅ 108ms
Format Validation.Distinct Id Is String ✅ 106ms
Format Validation.Token Is Present ✅ 106ms
Format Validation.Custom Properties Preserved ✅ 108ms
Format Validation.Event Has Timestamp ✅ 107ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 10ms
Retry Behavior.Retries On 503 ✅ 5311ms
Retry Behavior.Does Not Retry On 400 ✅ 2108ms
Retry Behavior.Does Not Retry On 401 ✅ 2109ms
Retry Behavior.Respects Retry After Header ✅ 8115ms
Retry Behavior.Implements Backoff ✅ 15723ms
Retry Behavior.Retries On 500 ✅ 5212ms
Retry Behavior.Retries On 502 ✅ 5212ms
Retry Behavior.Retries On 504 ✅ 5212ms
Retry Behavior.Max Retries Respected ✅ 15522ms
Deduplication.Generates Unique Uuids ✅ 111ms
Deduplication.Preserves Uuid On Retry ✅ 5212ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10418ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5214ms
Deduplication.No Duplicate Events In Batch ✅ 111ms
Deduplication.Different Events Have Different Uuids ✅ 106ms
Compression.Sends Gzip When Enabled ✅ 106ms
Batch Format.Uses Proper Batch Structure ✅ 105ms
Batch Format.Flush With No Events Sends Nothing ✅ 3ms
Batch Format.Multiple Events Batched Together ✅ 110ms
Error Handling.Does Not Retry On 403 ✅ 2106ms
Error Handling.Does Not Retry On 413 ✅ 2109ms
Error Handling.Retries On 408 ✅ 5209ms

Feature_Flags Tests

⚠️ 16/17 tests passed, 1 failed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 107ms
Request Payload.Flags Request Uses V2 Query Param ✅ 108ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 109ms
Request Payload.Flags Request Omits Authorization Header ✅ 109ms
Request Payload.Token In Flags Body Matches Init ✅ 107ms
Request Payload.Groups Round Trip ✅ 108ms
Request Payload.Groups Default To Empty Object ✅ 107ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 107ms
Request Payload.Disable Geoip Omitted Defaults To False ❌ 105ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 106ms
Request Lifecycle.No Flags Request On Init Alone ✅ 3ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 105ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 110ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 106ms
Retry Behavior.Retries Flags On 502 ✅ 244ms
Retry Behavior.Retries Flags On 504 ✅ 208ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 108ms

Failures

request_payload.disable_geoip_omitted_defaults_to_false

Field 'geoip_disable' not found in /flags request body at path 'geoip_disable'. Available keys: ['distinct_id', 'groups', 'person_properties', 'group_properties', 'flag_keys_to_evaluate', 'token']

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

posthog-ruby-sync Compliance Report

Date: 2026-10-26T11:43:02.923363+00:00
Duration: 56187ms

⚠️ Some Tests Failed

39/47 tests passed, 8 failed


Capture Tests

⚠️ 23/30 tests passed, 7 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 9ms
Format Validation.Event Has Uuid ✅ 7ms
Format Validation.Event Has Lib Properties ✅ 6ms
Format Validation.Distinct Id Is String ✅ 8ms
Format Validation.Token Is Present ✅ 7ms
Format Validation.Custom Properties Preserved ✅ 7ms
Format Validation.Event Has Timestamp ✅ 8ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 6ms
Retry Behavior.Retries On 503 ✅ 5369ms
Retry Behavior.Does Not Retry On 400 ❌ 6ms
Retry Behavior.Does Not Retry On 401 ❌ 7ms
Retry Behavior.Respects Retry After Header ✅ 8014ms
Retry Behavior.Implements Backoff ❌ 232ms
Retry Behavior.Retries On 500 ✅ 5154ms
Retry Behavior.Retries On 502 ✅ 5165ms
Retry Behavior.Retries On 504 ✅ 5144ms
Retry Behavior.Max Retries Respected ❌ 539ms
Deduplication.Generates Unique Uuids ✅ 20ms
Deduplication.Preserves Uuid On Retry ✅ 5112ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10268ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5167ms
Deduplication.No Duplicate Events In Batch ✅ 18ms
Deduplication.Different Events Have Different Uuids ✅ 9ms
Compression.Sends Gzip When Enabled ✅ 6ms
Batch Format.Uses Proper Batch Structure ✅ 6ms
Batch Format.Flush With No Events Sends Nothing ✅ 4ms
Batch Format.Multiple Events Batched Together ❌ 17ms
Error Handling.Does Not Retry On 403 ❌ 5ms
Error Handling.Does Not Retry On 413 ❌ 5ms
Error Handling.Retries On 408 ✅ 5156ms

Failures

retry_behavior.does_not_retry_on_400

500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'

retry_behavior.does_not_retry_on_401

500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'

retry_behavior.implements_backoff

500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'

retry_behavior.max_retries_respected

500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'

batch_format.multiple_events_batched_together

Expected 1 requests, got 5

error_handling.does_not_retry_on_403

500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'

error_handling.does_not_retry_on_413

500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'

Feature_Flags Tests

⚠️ 16/17 tests passed, 1 failed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 8ms
Request Payload.Flags Request Uses V2 Query Param ✅ 6ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 7ms
Request Payload.Flags Request Omits Authorization Header ✅ 6ms
Request Payload.Token In Flags Body Matches Init ✅ 8ms
Request Payload.Groups Round Trip ✅ 8ms
Request Payload.Groups Default To Empty Object ✅ 8ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 8ms
Request Payload.Disable Geoip Omitted Defaults To False ❌ 7ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 6ms
Request Lifecycle.No Flags Request On Init Alone ✅ 2ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 6ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 9ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 7ms
Retry Behavior.Retries Flags On 502 ✅ 136ms
Retry Behavior.Retries Flags On 504 ✅ 151ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 8ms

Failures

request_payload.disable_geoip_omitted_defaults_to_false

Field 'geoip_disable' not found in /flags request body at path 'geoip_disable'. Available keys: ['distinct_id', 'groups', 'person_properties', 'group_properties', 'flag_keys_to_evaluate', 'token']

@pauldambra

pauldambra commented Oct 17, 2026 •

Copy link
Copy Markdown
Member Author

Note

🤖 Automated comment by QA Swarm — not written by a human

Verdict: ✅ APPROVE (privacy round @ 98c5c85)

Restacked on the Rails client-owner fix. Job execution now binds the worker's PostHog.client, so a missing or disabled worker client adds no span IDs. No client is written into job data. Handler context and numeric-ID review threads are fixed and resolved.

Checks

Default and OpenTelemetry suites (1,330 each), RuboCop, and the public API check pass. An independent review accepted the change.

Earlier rounds

Verdict: ✅ APPROVE (round 1 @ 0be7d06)

No remaining actionable finding in the reviewed change. The review checked two-ID queue snapshots, identity precedence, retry cleanup, and exact Active Job span ownership. The recorded official instrumentation proof and full bundle results were reused.

Key findings

  • None remain in this revision. Existing tests and completed review coverage were reused where applicable.
  • Human review and the shared specification decision remain required. This comment does not approve a merge or release.

Convergence

No remaining duplicate finding.

Reviewer summaries

Reviewer Assessment
Router (GPT-6 (Codex)) Complexity HIGH, confidence HIGH. Existing review and validation evidence was reused where applicable. No new delegation was needed for this round.

Automated by QA Swarm. Human review is required. This comment does not approve a merge or release.

Generated-By: PostHog Desktop
Task-Id: 5681be44-4986-48db-b140-1bda88f75f96
Generated-By: PostHog Desktop
Task-Id: 5681be44-4986-48db-b140-1bda88f75f96
Generated-By: PostHog Desktop
Task-Id: 5681be44-4986-48db-b140-1bda88f75f96
Generated-By: PostHog Desktop
Task-Id: bfd18848-e498-4352-a58a-b9ba85de4198
@pauldambra
pauldambra force-pushed the posthog/active-job-context branch from 98c5c85 to 356d6ca Compare October 26, 2026 11:16
The Active Job test adapter on Rails 8.1 adds symbol keys such as
:priority next to the serialized "priority" key. JSON 3 rejects the
duplicate keys, so the latest-Rails job failed. Keep only the string
keys, which are what a queue stores.

Generated-By: PostHog Desktop
Task-Id: e73c8b25-9501-4f04-9fd8-e82415e1f9f9
@pauldambra
pauldambra merged commit 790d366 into main Oct 26, 2026
25 checks passed
@pauldambra
pauldambra deleted the posthog/active-job-context branch October 26, 2026 12:00
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.

2 participants