Skip to content

feat(rails): capture terminating runner exceptions - #341

Open
marandaneto wants to merge 5 commits into
mainfrom
feat/rails-runner-errors-zPpC27
Open

marandaneto wants to merge 5 commits into
mainfrom
feat/rails-runner-errors-zPpC27

Conversation

@marandaneto

@marandaneto marandaneto commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

💡 Motivation and Context

Terminating rails runner failures need to reach PostHog before the SDK shuts down. Executor reports can otherwise capture errors before the terminal boundary with non-terminal metadata, including successful exits.

This adds a runner marker and captures the terminal exception in the existing exit hook before the queue drains. Events use source rails.runner, level fatal, and an at_exit mechanism marked unhandled and non-synthetic. Normal completion and successful exits are ignored. Original exit status and error output are preserved, including when reporting fails or a later exit hook changes the terminal exception.

Unhandled runner executor reports retain the original exception object, reporter-resolved identity and safely serialized context until exit. When Rails converts an inline NameError or SyntaxError into exit 1, the handoff uses the original object for exclusions and deduplication. This also works with --skip-executor, where Rails emits no reporter event. It matches the cause type, status and actual Rails command caller rather than unwrapping arbitrary application SystemExit objects. Inline eval shares Rails' source filename, so filename matching alone is not used as proof of conversion. A different terminal exception does not inherit stale reporter context. The deferred record is consumed and cleared before terminal capture gates run.

auto_capture_exceptions remains opt-in and defaults to false. Only initialized, enabled clients are used. Exclusions, before_send, normal transport and existing client deduplication still apply. An exception already captured through the client keeps its earlier event. No new public option or method is exposed. The existing changeset selects a minor release for posthog-rails only.

Delivery is best-effort. Failures before runner hooks load, exit!, and termination that bypasses Ruby exit hooks cannot be captured. No separate runner-code, command-line argument or request-body properties are added. Existing reporter fields are preserved, not expanded. Exception messages and stack source context still follow the normal exception pipeline and may contain application data.

The touched behavior follows the pinned capture-exception, exception-event-metadata and before-send-hook specifications. No new divergence was identified. Unrelated existing conformance gaps are unchanged.

💚 How did you test it?

Local validation used Ruby 4.0.7 and Bundler 4.0.13. Full suites passed on Rails 7.2.3.1 and Rails 8.1.4 with latest cooldown-eligible dependencies resolved from this worktree's optional bundle. The historical Ruby/Rails matrix was not run locally.

  • Base bundle: 2207 examples, 0 failures, 40 pending for unavailable optional integrations and existing cases.
  • Optional OpenTelemetry bundle on Rails 7.2.3.1: 2207 examples, 0 failures, 2 existing pending cases.
  • Latest Rails 8.1.4 optional bundle: 2207 examples, 0 failures.
  • Focused runner and neighboring exception-mechanism suites: 52 examples, 0 failures on each Rails version.
  • RuboCop: 151 files, no offenses. Public API snapshot and whitespace checks passed.
  • Unit Tests for the current pushed SHA: all nine jobs passed, including Ruby 3.2/3.3/3.4, optional integrations, latest Rails, lint, API snapshot and gem builds.

The 38 runner examples boot real temporary Rails applications in subprocesses and inspect loopback queued HTTP delivery, exit status and stderr. They cover file and inline failures, original exception metadata, exclusions, manual-capture deduplication with and without executor reporting, reporter user_id and distinct_id, circular context serialization, context scrubbing, arbitrary SystemExit causes, later exit-hook errors and exits, successful exits, disabled capture/client, uninitialized clients, non-runner processes and reporting failures. File and inline cases compare error output byte-for-byte against disabled capture.

Before the fixes, eight added assertions failed on the reviewed head on Rails 7.2.3.1 and 8.1.4 for the reported exclusions, deduplication, original-object metadata and reporter-context regressions. A later controlled pre-fix replay of the then-current runner suite on Rails 8.1.4 produced nine expected failures, including the retained-identity assertion in the context-scrubbing case. Restoring the fix made all runner cases pass. A follow-up reproduced the same exclusion and duplicate-capture failures under --skip-executor: four failures among six new cases on both Rails versions before the fix, then all six passed. The two controls preserve intentional application exits 1 and 23 with a NameError cause. Dependency/setup failures were not counted as reproductions.

Diff-scoped cleanup and Authoring-mode test audit covered the complete runner suite and shared helper, with independent oracles from received HTTP events and Ruby process output/status. Exact final HEAD 43501a574edf9b2345a2a3525c3dc46c5ee68599 passed isolated autoreview with no findings. The fixes and pinned-main merge are signed and locally verified. GitHub also verifies the current follow-up commit signature as valid.

📝 Checklist

  • All commits are signed (see Commit signing)
  • 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

The existing hand-authored feature changeset was updated. No duplicate changeset or version bump was generated.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

marandaneto directed the work and is the human DRI. Pi's worker implementation subagent used file read/edit/write tools, bash, Git and GitHub CLI. The autoreview helper ran an isolated Pi review with claude-code/claude-opus-5-5. There is no public session URL.

The implementation keeps the existing exit hook and capture pipeline rather than adding a transport or public setting. Review feedback was reproduced with real Rails subprocesses before fixing the deferred handoff. This PR requires human review. No self-approval, merge or auto-merge is requested.

@marandaneto marandaneto self-assigned this Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

posthog-ruby-sync Compliance Report

Date: 2026-11-07T07:04:32.689414+00:00
Duration: 56039ms

⚠️ 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 ✅ 5ms
Format Validation.Event Has Lib Properties ✅ 5ms
Format Validation.Distinct Id Is String ✅ 8ms
Format Validation.Token Is Present ✅ 7ms
Format Validation.Custom Properties Preserved ✅ 6ms
Format Validation.Event Has Timestamp ✅ 7ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 6ms
Retry Behavior.Retries On 503 ✅ 5254ms
Retry Behavior.Does Not Retry On 400 ❌ 6ms
Retry Behavior.Does Not Retry On 401 ❌ 6ms
Retry Behavior.Respects Retry After Header ✅ 8011ms
Retry Behavior.Implements Backoff ❌ 340ms
Retry Behavior.Retries On 500 ✅ 5156ms
Retry Behavior.Retries On 502 ✅ 5116ms
Retry Behavior.Retries On 504 ✅ 5148ms
Retry Behavior.Max Retries Respected ❌ 607ms
Deduplication.Generates Unique Uuids ✅ 14ms
Deduplication.Preserves Uuid On Retry ✅ 5113ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10324ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5120ms
Deduplication.No Duplicate Events In Batch ✅ 16ms
Deduplication.Different Events Have Different Uuids ✅ 8ms
Compression.Sends Gzip When Enabled ✅ 5ms
Batch Format.Uses Proper Batch Structure ✅ 5ms
Batch Format.Flush With No Events Sends Nothing ✅ 3ms
Batch Format.Multiple Events Batched Together ❌ 14ms
Error Handling.Does Not Retry On 403 ❌ 4ms
Error Handling.Does Not Retry On 413 ❌ 4ms
Error Handling.Retries On 408 ✅ 5155ms

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 ✅ 8ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 8ms
Request Payload.Flags Request Omits Authorization Header ✅ 6ms
Request Payload.Token In Flags Body Matches Init ✅ 6ms
Request Payload.Groups Round Trip ✅ 6ms
Request Payload.Groups Default To Empty Object ✅ 5ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 6ms
Request Payload.Disable Geoip Omitted Defaults To False ❌ 5ms
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 ✅ 5ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 7ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 6ms
Retry Behavior.Retries Flags On 502 ✅ 138ms
Retry Behavior.Retries Flags On 504 ✅ 110ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 6ms

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 Nov 6, 2026 •

Copy link
Copy Markdown

posthog-ruby-async Compliance Report

Date: 2026-11-07T07:04:54.178509+00:00
Duration: 98450ms

⚠️ 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 ✅ 9ms
Format Validation.Event Has Uuid ✅ 105ms
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 ✅ 106ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 8ms
Retry Behavior.Retries On 503 ✅ 5311ms
Retry Behavior.Does Not Retry On 400 ✅ 2193ms
Retry Behavior.Does Not Retry On 401 ✅ 2109ms
Retry Behavior.Respects Retry After Header ✅ 8116ms
Retry Behavior.Implements Backoff ✅ 15614ms
Retry Behavior.Retries On 500 ✅ 5209ms
Retry Behavior.Retries On 502 ✅ 5210ms
Retry Behavior.Retries On 504 ✅ 5213ms
Retry Behavior.Max Retries Respected ✅ 15724ms
Deduplication.Generates Unique Uuids ✅ 111ms
Deduplication.Preserves Uuid On Retry ✅ 5211ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10311ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5214ms
Deduplication.No Duplicate Events In Batch ✅ 112ms
Deduplication.Different Events Have Different Uuids ✅ 107ms
Compression.Sends Gzip When Enabled ✅ 106ms
Batch Format.Uses Proper Batch Structure ✅ 106ms
Batch Format.Flush With No Events Sends Nothing ✅ 4ms
Batch Format.Multiple Events Batched Together ✅ 112ms
Error Handling.Does Not Retry On 403 ✅ 2109ms
Error Handling.Does Not Retry On 413 ✅ 2109ms
Error Handling.Retries On 408 ✅ 5212ms

Feature_Flags Tests

⚠️ 16/17 tests passed, 1 failed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 108ms
Request Payload.Flags Request Uses V2 Query Param ✅ 107ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 106ms
Request Payload.Flags Request Omits Authorization Header ✅ 106ms
Request Payload.Token In Flags Body Matches Init ✅ 107ms
Request Payload.Groups Round Trip ✅ 107ms
Request Payload.Groups Default To Empty Object ✅ 107ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 106ms
Request Payload.Disable Geoip Omitted Defaults To False ❌ 106ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 107ms
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 ✅ 111ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 108ms
Retry Behavior.Retries Flags On 502 ✅ 212ms
Retry Behavior.Retries Flags On 504 ✅ 247ms
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 Nov 6, 2026 •

Copy link
Copy Markdown

SDK performance: posthog-ruby

Benchmarks completed. No changes flagged for review.

Flags require at least a 5% change in the median and all six pairs agreeing on direction. This is a noise filter, not proof of a performance change or a CI gate.

View all results
Test Measurement Before PR With PR Change Assessment
Capture and deliver events (32-byte payload, burst)
Shard: capture
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Capture and deliver events (4,096-byte payload, burst)
Shard: capture
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Capture and deliver events (32-byte payload, steady)
Shard: capture
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Capture and deliver events (4,096-byte payload, steady)
Shard: capture
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Queue an event (32-byte payload)
Shard: capture
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Background serialization has no public enqueue completion boundary; use completed-delivery workloads for process-wide allocation counts.​; head:​ Background serialization has no public enqueue completion boundary; use completed-delivery workloads for process-wide allocation counts.​
Queue an event (32-byte payload)
Shard: capture
Allocations per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Background serialization has no public enqueue completion boundary; use completed-delivery workloads for process-wide allocation counts.​; head:​ Background serialization has no public enqueue completion boundary; use completed-delivery workloads for process-wide allocation counts.​
Queue an event (4,096-byte payload)
Shard: capture
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Background serialization has no public enqueue completion boundary; use completed-delivery workloads for process-wide allocation counts.​; head:​ Background serialization has no public enqueue completion boundary; use completed-delivery workloads for process-wide allocation counts.​
Queue an event (4,096-byte payload)
Shard: capture
Allocations per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Background serialization has no public enqueue completion boundary; use completed-delivery workloads for process-wide allocation counts.​; head:​ Background serialization has no public enqueue completion boundary; use completed-delivery workloads for process-wide allocation counts.​
Evaluate all 10 flags
Shard: flags-scaling
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Evaluate all 100 flags
Shard: flags-scaling
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Evaluate all 1,000 flags
Shard: flags-scaling
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Evaluate one boolean flag
Shard: flags-single
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Evaluate a cohort-filtered flag
Shard: flags-single
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Evaluate a flag (early match)
Shard: flags-single
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Evaluate a flag (late match)
Shard: flags-single
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Evaluate a flag (no match)
Shard: flags-single
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Evaluate a property-filtered flag
Shard: flags-single
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Evaluate a regex-filtered flag
Shard: flags-single
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Evaluate a rollout flag
Shard: flags-single
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Evaluate a multivariate flag
Shard: flags-single
Allocated memory per operation — — — unavailable; base:​ unsupported; head:​ unsupported; base:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​; head:​ Ruby has no cumulative allocated-byte counter here; retained ObjectSpace bytes are not allocation volume.​
Capture and deliver events (32-byte payload, burst)
Shard: capture
Allocations per operation 56.27 count/op 56.27 count/op No observed change Not flagged
Capture and deliver events (32-byte payload, burst)
Shard: capture
Time per operation 114.23 µs/op 114.04 µs/op 0.2% less time Not flagged
Capture and deliver events (32-byte payload, burst)
Shard: capture
Idle process memory (RSS) 28.33 MiB 28.35 MiB <0.1% more memory Not flagged
Capture and deliver events (32-byte payload, burst)
Shard: capture
Observed peak process memory (RSS) 31.09 MiB 31.11 MiB <0.1% more memory Not flagged
Capture and deliver events (32-byte payload, burst)
Shard: capture
Managed heap after settling 5.27 MiB 5.27 MiB <0.1% less memory Not flagged
Capture and deliver events (32-byte payload, burst)
Shard: capture
Process memory after settling (RSS) 30.33 MiB 30.35 MiB <0.1% more memory Not flagged
Capture and deliver events (32-byte payload, burst)
Shard: capture
Process CPU time per operation 30.94 µs/op 30.7 µs/op 0.8% less time Not flagged
Capture and deliver events (32-byte payload, burst)
Shard: capture
Lifetime peak process memory (RSS) 30.82 MiB 30.88 MiB 0.2% more memory Not flagged
Capture and deliver events (4,096-byte payload, burst)
Shard: capture
Allocations per operation 58.65 count/op 58.65 count/op No observed change Not flagged
Capture and deliver events (4,096-byte payload, burst)
Shard: capture
Time per operation 113.73 µs/op 113.66 µs/op <0.1% less time Not flagged
Capture and deliver events (4,096-byte payload, burst)
Shard: capture
Idle process memory (RSS) 33.12 MiB 33.1 MiB <0.1% less memory Not flagged
Capture and deliver events (4,096-byte payload, burst)
Shard: capture
Observed peak process memory (RSS) 41.84 MiB 41.78 MiB 0.1% less memory Not flagged
Capture and deliver events (4,096-byte payload, burst)
Shard: capture
Managed heap after settling 9.14 MiB 9.14 MiB <0.1% less memory Not flagged
Capture and deliver events (4,096-byte payload, burst)
Shard: capture
Process memory after settling (RSS) 39.67 MiB 39.61 MiB 0.2% less memory Not flagged
Capture and deliver events (4,096-byte payload, burst)
Shard: capture
Process CPU time per operation 42.65 µs/op 42.5 µs/op 0.4% less time Not flagged
Capture and deliver events (4,096-byte payload, burst)
Shard: capture
Lifetime peak process memory (RSS) 41.63 MiB 41.45 MiB 0.4% less memory Not flagged
Capture and deliver events (32-byte payload, steady)
Shard: capture
Allocations per operation 58.68 count/op 58.68 count/op No observed change Not flagged
Capture and deliver events (32-byte payload, steady)
Shard: capture
Time per operation 1.02 ms/op 1.02 ms/op <0.1% less time Not flagged
Capture and deliver events (32-byte payload, steady)
Shard: capture
Idle process memory (RSS) 28.23 MiB 28.27 MiB 0.2% more memory Not flagged
Capture and deliver events (32-byte payload, steady)
Shard: capture
Observed peak process memory (RSS) 30.37 MiB 30.38 MiB <0.1% more memory Not flagged
Capture and deliver events (32-byte payload, steady)
Shard: capture
Managed heap after settling 5.27 MiB 5.27 MiB <0.1% more memory Not flagged
Capture and deliver events (32-byte payload, steady)
Shard: capture
Process memory after settling (RSS) 30.37 MiB 30.38 MiB <0.1% more memory Not flagged
Capture and deliver events (32-byte payload, steady)
Shard: capture
Process CPU time per operation 40.67 µs/op 39.81 µs/op 2.1% less time Not flagged
Capture and deliver events (32-byte payload, steady)
Shard: capture
Lifetime peak process memory (RSS) 30.37 MiB 30.38 MiB <0.1% more memory Not flagged
Capture and deliver events (4,096-byte payload, steady)
Shard: capture
Allocations per operation 58.68 count/op 58.68 count/op No observed change Not flagged
Capture and deliver events (4,096-byte payload, steady)
Shard: capture
Time per operation 1.02 ms/op 1.02 ms/op <0.1% less time Not flagged
Capture and deliver events (4,096-byte payload, steady)
Shard: capture
Idle process memory (RSS) 33.05 MiB 33.03 MiB <0.1% less memory Not flagged
Capture and deliver events (4,096-byte payload, steady)
Shard: capture
Observed peak process memory (RSS) 40.64 MiB 40.43 MiB 0.5% less memory Not flagged
Capture and deliver events (4,096-byte payload, steady)
Shard: capture
Managed heap after settling 9.14 MiB 9.14 MiB <0.1% less memory Not flagged
Capture and deliver events (4,096-byte payload, steady)
Shard: capture
Process memory after settling (RSS) 40.54 MiB 40.13 MiB 1% less memory Not flagged
Capture and deliver events (4,096-byte payload, steady)
Shard: capture
Process CPU time per operation 49.54 µs/op 48.08 µs/op 2.9% less time Not flagged
Capture and deliver events (4,096-byte payload, steady)
Shard: capture
Lifetime peak process memory (RSS) 40.82 MiB 40.68 MiB 0.3% less memory Not flagged
Queue an event (32-byte payload)
Shard: capture
Caller-thread CPU time per operation 13.74 µs/op 13.6 µs/op 1% less time Not flagged
Queue an event (32-byte payload)
Shard: capture
Time per operation 13.74 µs/op 13.6 µs/op 1% less time Not flagged
Queue an event (4,096-byte payload)
Shard: capture
Caller-thread CPU time per operation 13.33 µs/op 13.47 µs/op 1% more time Not flagged
Queue an event (4,096-byte payload)
Shard: capture
Time per operation 13.33 µs/op 13.47 µs/op 1.1% more time Not flagged
Evaluate all 10 flags
Shard: flags-scaling
Allocations per operation 343.02 count/op 343.02 count/op No observed change Not flagged
Evaluate all 10 flags
Shard: flags-scaling
Time per operation 102.69 µs/op 102.8 µs/op 0.1% more time Not flagged
Evaluate all 10 flags
Shard: flags-scaling
Process CPU time per operation 102.69 µs/op 102.79 µs/op <0.1% more time Not flagged
Evaluate all 100 flags
Shard: flags-scaling
Allocations per operation 3,324 count/op 3,324 count/op No observed change Not flagged
Evaluate all 100 flags
Shard: flags-scaling
Time per operation 1.02 ms/op 1.01 ms/op 1.2% less time Not flagged
Evaluate all 100 flags
Shard: flags-scaling
Process CPU time per operation 1.02 ms/op 1.01 ms/op 1.2% less time Not flagged
Evaluate all 1,000 flags
Shard: flags-scaling
Allocations per operation 33,113.59 count/op 33,113.59 count/op No observed change Not flagged
Evaluate all 1,000 flags
Shard: flags-scaling
Time per operation 10.66 ms/op 10.48 ms/op 1.6% less time Not flagged
Evaluate all 1,000 flags
Shard: flags-scaling
Process CPU time per operation 10.65 ms/op 10.48 ms/op 1.6% less time Not flagged
Evaluate 1,000 flags under concurrent load
Shard: flags-memory
Idle process memory (RSS) 39.3 MiB 39.32 MiB <0.1% more memory Not flagged
Evaluate 1,000 flags under concurrent load
Shard: flags-memory
Observed peak process memory (RSS) 40.69 MiB 40.89 MiB 0.5% more memory Not flagged
Evaluate 1,000 flags under concurrent load
Shard: flags-memory
Managed heap after settling 7.97 MiB 7.97 MiB <0.1% less memory Not flagged
Evaluate 1,000 flags under concurrent load
Shard: flags-memory
Process memory after settling (RSS) 39.49 MiB 39.45 MiB <0.1% less memory Not flagged
Evaluate 1,000 flags under concurrent load
Shard: flags-memory
Lifetime peak process memory (RSS) 40.48 MiB 40.59 MiB 0.3% more memory Not flagged
Refresh 1,000 flag definitions
Shard: flags-memory
Idle process memory (RSS) 38.17 MiB 38.14 MiB <0.1% less memory Not flagged
Refresh 1,000 flag definitions
Shard: flags-memory
Observed peak process memory (RSS) 41.31 MiB 41.27 MiB <0.1% less memory Not flagged
Refresh 1,000 flag definitions
Shard: flags-memory
Managed heap after settling 9.48 MiB 9.48 MiB <0.1% less memory Not flagged
Refresh 1,000 flag definitions
Shard: flags-memory
Process memory after settling (RSS) 41.29 MiB 41.27 MiB <0.1% less memory Not flagged
Refresh 1,000 flag definitions
Shard: flags-memory
Lifetime peak process memory (RSS) 41.29 MiB 41.27 MiB <0.1% less memory Not flagged
Evaluate one boolean flag
Shard: flags-single
Allocations per operation 38 count/op 38 count/op No observed change Not flagged
Evaluate one boolean flag
Shard: flags-single
Time per operation 9.11 µs/op 9.07 µs/op 0.5% less time Not flagged
Evaluate one boolean flag
Shard: flags-single
Process CPU time per operation 9.11 µs/op 9.07 µs/op 0.5% less time Not flagged
Evaluate a cohort-filtered flag
Shard: flags-single
Allocations per operation 38 count/op 38 count/op No observed change Not flagged
Evaluate a cohort-filtered flag
Shard: flags-single
Time per operation 11.39 µs/op 11.37 µs/op 0.2% less time Not flagged
Evaluate a cohort-filtered flag
Shard: flags-single
Process CPU time per operation 11.39 µs/op 11.37 µs/op 0.2% less time Not flagged
Evaluate a flag (early match)
Shard: flags-single
Allocations per operation 55 count/op 55 count/op No observed change Not flagged
Evaluate a flag (early match)
Shard: flags-single
Time per operation 15.98 µs/op 16.03 µs/op 0.3% more time Not flagged
Evaluate a flag (early match)
Shard: flags-single
Process CPU time per operation 15.98 µs/op 16.03 µs/op 0.3% more time Not flagged
Evaluate a flag (late match)
Shard: flags-single
Allocations per operation 76 count/op 76 count/op No observed change Not flagged
Evaluate a flag (late match)
Shard: flags-single
Time per operation 22.52 µs/op 22.72 µs/op 0.9% more time Not flagged
Evaluate a flag (late match)
Shard: flags-single
Process CPU time per operation 22.52 µs/op 22.72 µs/op 0.9% more time Not flagged
Evaluate a flag (no match)
Shard: flags-single
Allocations per operation 67 count/op 67 count/op No observed change Not flagged
Evaluate a flag (no match)
Shard: flags-single
Time per operation 20.46 µs/op 20.66 µs/op 1% more time Not flagged
Evaluate a flag (no match)
Shard: flags-single
Process CPU time per operation 20.46 µs/op 20.65 µs/op 1% more time Not flagged
Evaluate a property-filtered flag
Shard: flags-single
Allocations per operation 34 count/op 34 count/op No observed change Not flagged
Evaluate a property-filtered flag
Shard: flags-single
Time per operation 9.43 µs/op 9.44 µs/op 0.1% more time Not flagged
Evaluate a property-filtered flag
Shard: flags-single
Process CPU time per operation 9.43 µs/op 9.44 µs/op 0.1% more time Not flagged
Evaluate a regex-filtered flag
Shard: flags-single
Allocations per operation 99 count/op 99 count/op <0.1% fewer Not flagged
Evaluate a regex-filtered flag
Shard: flags-single
Time per operation 25.5 µs/op 25.58 µs/op 0.3% more time Not flagged
Evaluate a regex-filtered flag
Shard: flags-single
Process CPU time per operation 25.5 µs/op 25.57 µs/op 0.3% more time Not flagged
Evaluate a rollout flag
Shard: flags-single
Allocations per operation 31 count/op 31 count/op No observed change Not flagged
Evaluate a rollout flag
Shard: flags-single
Time per operation 8.22 µs/op 8.15 µs/op 0.9% less time Not flagged
Evaluate a rollout flag
Shard: flags-single
Process CPU time per operation 8.22 µs/op 8.15 µs/op 0.9% less time Not flagged
Evaluate a multivariate flag
Shard: flags-single
Allocations per operation 37 count/op 37 count/op No observed change Not flagged
Evaluate a multivariate flag
Shard: flags-single
Time per operation 10.47 µs/op 10.34 µs/op 1.2% less time Not flagged
Evaluate a multivariate flag
Shard: flags-single
Process CPU time per operation 10.47 µs/op 10.34 µs/op 1.2% less time Not flagged

Values are medians of paired measurements. Not flagged does not mean unchanged. Unavailable means a measurement could not be provided, not zero. Estimated allocation measurements retain their instrumentation uncertainty. RSS measures the whole process, not just SDK allocations; observed peaks are sampled checkpoints. Paired ranges in the diagnostics describe variability, not confidence intervals.

Each shard uses its own runner; compare Before PR against With PR within a row, not across shards.

Compared 81093eaf8199fee1330758bb9940a4d17b2acfdc → 43501a574edf9b2345a2a3525c3dc46c5ee68599.

Full report and diagnostics

102 metric rows; 102 rows shown in all results. Omitted or shortened data remain in comparison.json, including per-runner environments, resource limits, measurement methods, samples and checkpoints.

@marandaneto
marandaneto marked this pull request as ready for review November 6, 2026 14:52
@marandaneto
marandaneto requested a review from a team as a code owner November 6, 2026 14:52

@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 6cd4090: 1 Should fix.

See this review and its findings in PostHog Review. Comment @posthog review --deep for a Deep review. A Deep review takes longer and looks deeper than a Standard review.

@dustinbyrne dustinbyrne 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.

The ordinary runner paths and shutdown ordering look sound. Two regressions remain in the deferred handoff: inline Rails-converted errors bypass exclusions/deduplication, and runner reports lose existing user/context enrichment. Both reproduce with equivalent base/head tests on Rails 7.2.4 and 8.1.4. Details and regression cases are inline.

AI-assisted review.

@dustinbyrne
dustinbyrne requested a review from a team November 6, 2026 17:49
@marandaneto

marandaneto commented Nov 7, 2026 •

Copy link
Copy Markdown
Member Author

Both deferred-runner review concerns are addressed in signed commit 73195fa, now pushed at 9c55f75 after merging pinned main. Real Rails 7.2.3.1 and 8.1.4 subprocess/HTTP regressions failed before the fix and now pass. The original inline error is retained for exclusions/deduplication, and existing reporter identity/context survives deferral without applying stale context to a later failure. Full base, optional and latest-Rails suites passed locally. All nine Unit Tests jobs for the pushed SHA passed: https://github.com/PostHog/posthog-ruby/actions/runs/42347049445. The PR description contains exact counts and compatibility limits.

@veria-ai

veria-ai Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

This branch has not been deployed

No deployments
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