Skip to content

feat(rails): capture Action Cable exceptions - #335

Open
marandaneto wants to merge 4 commits into
mainfrom
feat/rails-action-cable-zPpC27
Open

marandaneto wants to merge 4 commits into
mainfrom
feat/rails-action-cable-zPpC27

Conversation

@marandaneto

@marandaneto marandaneto commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

💡 Motivation and Context

Action Cable runs connection callbacks and channel actions outside the normal HTTP exception middleware. This adds automatic capture around connection open/close, channel subscribe/unsubscribe, and dispatched channel actions. It uses the existing auto_capture_exceptions option, which remains false by default, and respects excluded_exceptions. No new public configuration or methods are added. The existing changeset selects a minor release of posthog-rails only.

Each operation gets isolated context from the connection environment and restores the caller's context afterward. Authenticated controller or connection users take precedence over sanitized PostHog header identity. Existing user lookup options apply. capture_user_context = false disables user lookup, and use_tracing_headers = false disables header correlation. A controller resolver is never passed a connection. Explicit capture IDs and properties keep their existing precedence.

Channel parameters and action data combine application filter_parameters with the effective connection's config.filter_parameters, including nested values and supported Proc rules, before serialization. Duplicate inherited rules run once, so shared non-idempotent Proc filters cannot undo their own scrubbing. Connections without filter configuration fall back to application filters. Raw environments, cookies, authorization headers, subscription identifiers, and URL query strings are not attached. Applications should review filtering and before_send for their own sensitive data.

Channel failures dispatched through Subscriptions#execute_command wait until connection rescue handling finishes. Errors consumed by application handlers, including channel-local and connection rescue_from, are not captured by these boundaries, even with report_rescued_exceptions = true. That option does not enable handled Action Cable events. Unhandled channel errors retain channel/action context, including when the dispatcher logs without re-raising. Direct channel calls capture escaping errors without invoking connection handlers. Returns, escaping exceptions, handler invocation counts, and capture deduplication remain unchanged.

Action Cable remains optional and loads lazily. Its added dependency is development/test-only. The wrappers do not create or enrich spans. Rails 5.2 has a fallback load hook, but local validation covered Rails 7.2.3.1 and 8.1.4, not a Rails 5.2 application.

The relevant SDK specs cover exception metadata, tracing headers, exception capture, logs context, and before_send. None defines an Action Cable-specific API. New events use source rails.action_cable, level error, and a middleware mechanism with handled: false. One contract difference remains: this integration passes source and level through the existing Ruby exception property bag rather than a typed internal metadata channel. This PR does not redesign the core exception API.

💚 How did you test it?

Validated on Ruby 4.0.7 and Bundler 4.0.13 after a signed merge of base 81093eaf8199fee1330758bb9940a4d17b2acfdc:

  • Reproduction on reviewed head 72ba70aa210dad9f0bdb258e5825824d58a28fbc: the final 37 Action Cable examples had 8 expected behavioral failures. Both payloads leaked Cable-only nested secrets, consumed subscribe/message/unsubscribe errors were captured, and escaping handler errors retained the wrong exception. Those 37 examples pass with the fix.
  • The shared-Proc regression failed on previous published HEAD a5e35b6 with 1 example, 1 failure: a reversing Proc ran twice and restored the original value. bundle exec rspec spec/posthog/rails/action_cable_spec.rb --example 'runs a shared application Proc' now passes with 1 example, 0 failures. The full focused Action Cable suite passes with 38 examples, 0 failures.
  • Real server, connection, channel, and execute_command coverage includes handler invocation counts, direct calls, channel-local rescue, authenticated identity, successful returns, concurrent dispatch isolation, client gates, filtering failures, deduplication, and before_send drops. Subprocess cases cover application boot and lazy loading.
  • Full Rails 7.2.3.1 base suite: 2207 examples, 0 failures, 40 pending.
  • Full Rails 7.2.3.1 optional OpenTelemetry suite: 2207 examples, 0 failures, 2 pending.
  • Full Rails 8.1.4 optional OpenTelemetry suite with latest eligible dependencies: 2207 examples, 0 failures, no pending.
  • bundle exec rubocop: 152 files, no offenses.
  • bundle exec rake public_api:check and git diff --check: passed. No public API snapshot change was needed.

The diff-scoped cleanup and Authoring test audit found no blockers. Tests use independently expected event payloads and actual framework lifecycle paths. The historical Ruby/Rails matrix was not run locally.

Exact-HEAD autoreview on 983872c1b0d5239e7e332f133e0a4a590570dbe0 against origin/main completed with no findings.

📝 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 .changeset/rails-action-cable-exceptions.md is included. pnpm changeset was not run, and no duplicate changeset or feature version bump was added.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Human DRI: @marandaneto. Pi's worker prepared the feature and addressed approved review feedback using local file tools, Bash, Git, GitHub CLI, RSpec, RuboCop, Rake, and the autoreview helper with claude-code/claude-opus-5-5. There is no public session URL.

The implementation keeps automatic capture opt-in and preserves the existing policy of not autocapturing application-consumed Cable errors. It waits for actual connection handler execution rather than checking whether a handler exists. This PR requires human review before merge.

@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:55.130558+00:00
Duration: 55975ms

⚠️ 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 ✅ 7ms
Format Validation.Event Has Uuid ✅ 4ms
Format Validation.Event Has Lib Properties ✅ 4ms
Format Validation.Distinct Id Is String ✅ 6ms
Format Validation.Token Is Present ✅ 4ms
Format Validation.Custom Properties Preserved ✅ 4ms
Format Validation.Event Has Timestamp ✅ 4ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 4ms
Retry Behavior.Retries On 503 ✅ 5292ms
Retry Behavior.Does Not Retry On 400 ❌ 6ms
Retry Behavior.Does Not Retry On 401 ❌ 5ms
Retry Behavior.Respects Retry After Header ✅ 8014ms
Retry Behavior.Implements Backoff ❌ 351ms
Retry Behavior.Retries On 500 ✅ 5111ms
Retry Behavior.Retries On 502 ✅ 5112ms
Retry Behavior.Retries On 504 ✅ 5140ms
Retry Behavior.Max Retries Respected ❌ 567ms
Deduplication.Generates Unique Uuids ✅ 14ms
Deduplication.Preserves Uuid On Retry ✅ 5158ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10365ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5116ms
Deduplication.No Duplicate Events In Batch ✅ 15ms
Deduplication.Different Events Have Different Uuids ✅ 9ms
Compression.Sends Gzip When Enabled ✅ 5ms
Batch Format.Uses Proper Batch Structure ✅ 4ms
Batch Format.Flush With No Events Sends Nothing ✅ 2ms
Batch Format.Multiple Events Batched Together ❌ 11ms
Error Handling.Does Not Retry On 403 ❌ 3ms
Error Handling.Does Not Retry On 413 ❌ 3ms
Error Handling.Retries On 408 ✅ 5146ms

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 ✅ 6ms
Request Payload.Flags Request Uses V2 Query Param ✅ 5ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 4ms
Request Payload.Flags Request Omits Authorization Header ✅ 5ms
Request Payload.Token In Flags Body Matches Init ✅ 4ms
Request Payload.Groups Round Trip ✅ 4ms
Request Payload.Groups Default To Empty Object ✅ 5ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 7ms
Request Payload.Disable Geoip Omitted Defaults To False ❌ 8ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 4ms
Request Lifecycle.No Flags Request On Init Alone ✅ 2ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 4ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 6ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 4ms
Retry Behavior.Retries Flags On 502 ✅ 107ms
Retry Behavior.Retries Flags On 504 ✅ 152ms
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:05:35.427397+00:00
Duration: 98462ms

⚠️ 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 ✅ 106ms
Format Validation.Event Has Lib Properties ✅ 107ms
Format Validation.Distinct Id Is String ✅ 107ms
Format Validation.Token Is Present ✅ 107ms
Format Validation.Custom Properties Preserved ✅ 106ms
Format Validation.Event Has Timestamp ✅ 107ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 8ms
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 ✅ 8116ms
Retry Behavior.Implements Backoff ✅ 15719ms
Retry Behavior.Retries On 500 ✅ 5213ms
Retry Behavior.Retries On 502 ✅ 5212ms
Retry Behavior.Retries On 504 ✅ 5214ms
Retry Behavior.Max Retries Respected ✅ 15618ms
Deduplication.Generates Unique Uuids ✅ 112ms
Deduplication.Preserves Uuid On Retry ✅ 5211ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10410ms
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 ✅ 105ms
Batch Format.Uses Proper Batch Structure ✅ 106ms
Batch Format.Flush With No Events Sends Nothing ✅ 5ms
Batch Format.Multiple Events Batched Together ✅ 111ms
Error Handling.Does Not Retry On 403 ✅ 2108ms
Error Handling.Does Not Retry On 413 ✅ 2109ms
Error Handling.Retries On 408 ✅ 5213ms

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 ✅ 106ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 107ms
Request Payload.Flags Request Omits Authorization Header ✅ 106ms
Request Payload.Token In Flags Body Matches Init ✅ 107ms
Request Payload.Groups Round Trip ✅ 106ms
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 ❌ 107ms
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 ✅ 106ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 113ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 107ms
Retry Behavior.Retries Flags On 502 ✅ 237ms
Retry Behavior.Retries Flags On 504 ✅ 209ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 109ms

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 108.18 µs/op 108.27 µs/op <0.1% more time Not flagged
Capture and deliver events (32-byte payload, burst)
Shard: capture
Idle process memory (RSS) 28.36 MiB 28.36 MiB No observed change Not flagged
Capture and deliver events (32-byte payload, burst)
Shard: capture
Observed peak process memory (RSS) 31.12 MiB 31.12 MiB No observed change Not flagged
Capture and deliver events (32-byte payload, burst)
Shard: capture
Managed heap after settling 5.28 MiB 5.28 MiB <0.1% less memory Not flagged
Capture and deliver events (32-byte payload, burst)
Shard: capture
Process memory after settling (RSS) 30.35 MiB 30.35 MiB No observed change Not flagged
Capture and deliver events (32-byte payload, burst)
Shard: capture
Process CPU time per operation 18.49 µs/op 18.51 µs/op 0.1% more time Not flagged
Capture and deliver events (32-byte payload, burst)
Shard: capture
Lifetime peak process memory (RSS) 30.92 MiB 30.98 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 108.06 µs/op 107.85 µs/op 0.2% less time Not flagged
Capture and deliver events (4,096-byte payload, burst)
Shard: capture
Idle process memory (RSS) 33.18 MiB 33.21 MiB <0.1% more memory Not flagged
Capture and deliver events (4,096-byte payload, burst)
Shard: capture
Observed peak process memory (RSS) 42.38 MiB 42.78 MiB 0.9% more memory Not flagged
Capture and deliver events (4,096-byte payload, burst)
Shard: capture
Managed heap after settling 9.15 MiB 9.15 MiB <0.1% more memory Not flagged
Capture and deliver events (4,096-byte payload, burst)
Shard: capture
Process memory after settling (RSS) 40.67 MiB 39.74 MiB 2.3% less memory Not flagged
Capture and deliver events (4,096-byte payload, burst)
Shard: capture
Process CPU time per operation 25.9 µs/op 25.14 µs/op 3% less time Not flagged
Capture and deliver events (4,096-byte payload, burst)
Shard: capture
Lifetime peak process memory (RSS) 42.21 MiB 42.62 MiB 1% more 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.01 ms/op 1.01 ms/op <0.1% less time Not flagged
Capture and deliver events (32-byte payload, steady)
Shard: capture
Idle process memory (RSS) 28.29 MiB 28.26 MiB 0.1% less memory Not flagged
Capture and deliver events (32-byte payload, steady)
Shard: capture
Observed peak process memory (RSS) 30.41 MiB 30.37 MiB 0.1% less memory Not flagged
Capture and deliver events (32-byte payload, steady)
Shard: capture
Managed heap after settling 5.28 MiB 5.28 MiB <0.1% more memory Not flagged
Capture and deliver events (32-byte payload, steady)
Shard: capture
Process memory after settling (RSS) 30.41 MiB 30.37 MiB 0.1% less memory Not flagged
Capture and deliver events (32-byte payload, steady)
Shard: capture
Process CPU time per operation 29.46 µs/op 29.01 µs/op 1.5% less time Not flagged
Capture and deliver events (32-byte payload, steady)
Shard: capture
Lifetime peak process memory (RSS) 30.41 MiB 30.37 MiB 0.1% less 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.01 ms/op 1.01 ms/op <0.1% less time Not flagged
Capture and deliver events (4,096-byte payload, steady)
Shard: capture
Idle process memory (RSS) 33.25 MiB 33.22 MiB <0.1% less memory Not flagged
Capture and deliver events (4,096-byte payload, steady)
Shard: capture
Observed peak process memory (RSS) 40.55 MiB 40.55 MiB <0.1% less memory Not flagged
Capture and deliver events (4,096-byte payload, steady)
Shard: capture
Managed heap after settling 9.15 MiB 9.15 MiB <0.1% less memory Not flagged
Capture and deliver events (4,096-byte payload, steady)
Shard: capture
Process memory after settling (RSS) 40.19 MiB 40.21 MiB <0.1% more memory Not flagged
Capture and deliver events (4,096-byte payload, steady)
Shard: capture
Process CPU time per operation 33.17 µs/op 32.93 µs/op 0.7% less time Not flagged
Capture and deliver events (4,096-byte payload, steady)
Shard: capture
Lifetime peak process memory (RSS) 40.87 MiB 40.86 MiB <0.1% less memory Not flagged
Queue an event (32-byte payload)
Shard: capture
Caller-thread CPU time per operation 7.39 µs/op 7.42 µs/op 0.4% more time Not flagged
Queue an event (32-byte payload)
Shard: capture
Time per operation 7.39 µs/op 7.42 µs/op 0.4% more time Not flagged
Queue an event (4,096-byte payload)
Shard: capture
Caller-thread CPU time per operation 7.6 µs/op 7.47 µs/op 1.7% less time Not flagged
Queue an event (4,096-byte payload)
Shard: capture
Time per operation 7.6 µs/op 7.47 µs/op 1.7% less 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 141.69 µs/op 141.69 µs/op <0.1% more time Not flagged
Evaluate all 10 flags
Shard: flags-scaling
Process CPU time per operation 141.68 µs/op 141.67 µs/op <0.1% less 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.38 ms/op 1.38 ms/op 0.2% less time Not flagged
Evaluate all 100 flags
Shard: flags-scaling
Process CPU time per operation 1.38 ms/op 1.38 ms/op 0.2% less time Not flagged
Evaluate all 1,000 flags
Shard: flags-scaling
Allocations per operation 33,113.56 count/op 33,113.59 count/op <0.1% more Not flagged
Evaluate all 1,000 flags
Shard: flags-scaling
Time per operation 14.37 ms/op 14.38 ms/op <0.1% more time Not flagged
Evaluate all 1,000 flags
Shard: flags-scaling
Process CPU time per operation 14.37 ms/op 14.38 ms/op <0.1% more time Not flagged
Evaluate 1,000 flags under concurrent load
Shard: flags-memory
Idle process memory (RSS) 38.86 MiB 38.86 MiB <0.1% less memory Not flagged
Evaluate 1,000 flags under concurrent load
Shard: flags-memory
Observed peak process memory (RSS) 40.28 MiB 40.36 MiB 0.2% 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.32 MiB 39.27 MiB 0.1% less memory Not flagged
Evaluate 1,000 flags under concurrent load
Shard: flags-memory
Lifetime peak process memory (RSS) 39.99 MiB 40.16 MiB 0.4% more memory Not flagged
Refresh 1,000 flag definitions
Shard: flags-memory
Idle process memory (RSS) 38.09 MiB 38.12 MiB <0.1% more memory Not flagged
Refresh 1,000 flag definitions
Shard: flags-memory
Observed peak process memory (RSS) 41.43 MiB 41.44 MiB <0.1% more 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.42 MiB 41.44 MiB <0.1% more memory Not flagged
Refresh 1,000 flag definitions
Shard: flags-memory
Lifetime peak process memory (RSS) 41.43 MiB 41.44 MiB <0.1% more 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.43 µs/op 9.46 µs/op 0.3% more time Not flagged
Evaluate one boolean flag
Shard: flags-single
Process CPU time per operation 9.43 µs/op 9.46 µs/op 0.3% more 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.61 µs/op 11.75 µs/op 1.2% more time Not flagged
Evaluate a cohort-filtered flag
Shard: flags-single
Process CPU time per operation 11.61 µs/op 11.75 µs/op 1.2% more 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 16.41 µs/op 16.43 µs/op 0.2% more time Not flagged
Evaluate a flag (early match)
Shard: flags-single
Process CPU time per operation 16.41 µs/op 16.43 µs/op 0.1% 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.92 µs/op 22.97 µs/op 0.2% more time Not flagged
Evaluate a flag (late match)
Shard: flags-single
Process CPU time per operation 22.91 µs/op 22.96 µs/op 0.2% 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.93 µs/op 20.98 µs/op 0.2% more time Not flagged
Evaluate a flag (no match)
Shard: flags-single
Process CPU time per operation 20.92 µs/op 20.97 µs/op 0.2% 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.62 µs/op 9.72 µs/op 1.1% more time Not flagged
Evaluate a property-filtered flag
Shard: flags-single
Process CPU time per operation 9.61 µs/op 9.72 µs/op 1.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 26.54 µs/op 26.33 µs/op 0.8% less time Not flagged
Evaluate a regex-filtered flag
Shard: flags-single
Process CPU time per operation 26.54 µs/op 26.32 µs/op 0.8% less 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.48 µs/op 8.49 µs/op <0.1% more time Not flagged
Evaluate a rollout flag
Shard: flags-single
Process CPU time per operation 8.48 µs/op 8.49 µs/op <0.1% more 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.61 µs/op 10.56 µs/op 0.4% less time Not flagged
Evaluate a multivariate flag
Shard: flags-single
Process CPU time per operation 10.6 µs/op 10.56 µs/op 0.4% 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 → 983872c1b0d5239e7e332f133e0a4a590570dbe0.

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:48
@marandaneto
marandaneto requested a review from a team as a code owner November 6, 2026 14:48

@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 72ba70a: 1 Must fix, 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.

@veria-ai

veria-ai Bot commented Nov 6, 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

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

Two reproduced cases need correction before approval: Cable-only sensitive-parameter filters are omitted, and exceptions consumed by a connection's rescue_from are emitted as unhandled. The existing Action Cable examples pass but do not cover these cases.

AI-assisted review.

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

marandaneto commented Nov 7, 2026 •

Copy link
Copy Markdown
Member Author

Addressed the filtering concern in comment 6687743695 and duplicate inline reports in 43046fc plus 983872c, now pushed with verified signatures. Application and effective Cable filters apply to both payloads, including nested values and supported Proc rules. Shared inherited rules are deduplicated to avoid undoing non-idempotent Proc scrubbing. Initial RED on reviewed code: 37 examples, 8 failures, including both leaking payloads. The shared-Proc example separately failed on a5e35b6 (1 example, 1 failure) and passes with the follow-up. Final bundle exec rspec spec/posthog/rails/action_cable_spec.rb: 38 examples, 0 failures. Connection rescue handling is also respected before capture. Full base, optional OpenTelemetry, and Rails 8.1.4 suites pass 2207 examples each, with 40/2/0 pending respectively. All addressed inline threads have commit-specific evidence and are resolved.

@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 a5e35b6: 1 Must fix.

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