fix: preserve escaped characters in fork-curl gzip payloads - #250
Conversation
posthog-php-lib_curl Compliance ReportDate: 2026-09-27T13:36:23.778037+00:00 ✅ All Tests Passed!47/47 tests passed Capture Tests✅ 30/30 tests passed View Details
Feature_Flags Tests✅ 17/17 tests passed View Details
|
posthog-php-fork_curl Compliance ReportDate: 2026-09-27T13:36:19.483873+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 37ms |
| Format Validation.Event Has Uuid | ✅ | 530ms |
| Format Validation.Event Has Lib Properties | ✅ | 531ms |
| Format Validation.Distinct Id Is String | ✅ | 531ms |
| Format Validation.Token Is Present | ✅ | 532ms |
| Format Validation.Custom Properties Preserved | ✅ | 533ms |
| Format Validation.Event Has Timestamp | ✅ | 532ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 532ms |
| Retry Behavior.Retries On 503 | ❌ | 5535ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2536ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2535ms |
| Retry Behavior.Respects Retry After Header | ❌ | 5537ms |
| Retry Behavior.Implements Backoff | ❌ | 15549ms |
| Retry Behavior.Retries On 500 | ❌ | 5539ms |
| Retry Behavior.Retries On 502 | ❌ | 5539ms |
| Retry Behavior.Retries On 504 | ❌ | 5536ms |
| Retry Behavior.Max Retries Respected | ❌ | 15551ms |
| Deduplication.Generates Unique Uuids | ✅ | 541ms |
| Deduplication.Preserves Uuid On Retry | ❌ | 5537ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ❌ | 10544ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ❌ | 5542ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 539ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 533ms |
| Compression.Sends Gzip When Enabled | ✅ | 535ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 532ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 519ms |
| Batch Format.Multiple Events Batched Together | ✅ | 522ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2532ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2536ms |
| Error Handling.Retries On 408 | ❌ | 5537ms |
Failures
retry_behavior.retries_on_503
Expected at least 3 requests, got 1
retry_behavior.respects_retry_after_header
Expected at least 2 requests, got 1
retry_behavior.implements_backoff
Expected at least 3 requests, got 1
retry_behavior.retries_on_500
Expected at least 2 requests, got 1
retry_behavior.retries_on_502
Expected at least 2 requests, got 1
retry_behavior.retries_on_504
Expected at least 2 requests, got 1
retry_behavior.max_retries_respected
Expected 4 requests, got 1
deduplication.preserves_uuid_on_retry
Need at least 2 requests to check retry
deduplication.preserves_uuid_and_timestamp_on_retry
Expected at least 3 requests, got 1
deduplication.preserves_uuid_and_timestamp_on_batch_retry
Expected at least 2 requests, got 1
error_handling.retries_on_408
Expected at least 2 requests, got 1
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 524ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 524ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 523ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 523ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 523ms |
| Request Payload.Groups Round Trip | ✅ | 523ms |
| Request Payload.Groups Default To Empty Object | ✅ | 525ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 526ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 522ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 522ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 518ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 517ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 525ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 525ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 626ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 625ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 533ms |
|
[Medium risk] Fixes payload encoding in the event delivery system. The PR appears safe to merge; the test-portability issue is non-blocking. Reviews (1) · Last reviewed commit: "fix: preserve escaped characters in fork..." |
posthog-php-socket Compliance ReportDate: 2026-09-27T13:37:04.975582+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 27ms |
| Format Validation.Event Has Uuid | ✅ | 524ms |
| Format Validation.Event Has Lib Properties | ✅ | 525ms |
| Format Validation.Distinct Id Is String | ✅ | 524ms |
| Format Validation.Token Is Present | ✅ | 525ms |
| Format Validation.Custom Properties Preserved | ✅ | 524ms |
| Format Validation.Event Has Timestamp | ✅ | 525ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 524ms |
| Retry Behavior.Retries On 503 | ❌ | 9233ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2527ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2525ms |
| Retry Behavior.Respects Retry After Header | ❌ | 9234ms |
| Retry Behavior.Implements Backoff | ❌ | 19246ms |
| Retry Behavior.Retries On 500 | ❌ | 9237ms |
| Retry Behavior.Retries On 502 | ❌ | 9236ms |
| Retry Behavior.Retries On 504 | ❌ | 9230ms |
| Retry Behavior.Max Retries Respected | ❌ | 18751ms |
| Deduplication.Generates Unique Uuids | ✅ | 534ms |
| Deduplication.Preserves Uuid On Retry | ❌ | 9234ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ❌ | 14240ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ❌ | 9241ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 531ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 525ms |
| Compression.Sends Gzip When Enabled | ✅ | 524ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 523ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 519ms |
| Batch Format.Multiple Events Batched Together | ✅ | 513ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2525ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2527ms |
| Error Handling.Retries On 408 | ❌ | 5529ms |
Failures
retry_behavior.retries_on_503
Expected at least 3 requests, got 1
retry_behavior.respects_retry_after_header
Expected at least 2 requests, got 1
retry_behavior.implements_backoff
Expected at least 3 requests, got 1
retry_behavior.retries_on_500
Expected at least 2 requests, got 1
retry_behavior.retries_on_502
Expected at least 2 requests, got 1
retry_behavior.retries_on_504
Expected at least 2 requests, got 1
retry_behavior.max_retries_respected
Expected 4 requests, got 1
deduplication.preserves_uuid_on_retry
Need at least 2 requests to check retry
deduplication.preserves_uuid_and_timestamp_on_retry
Expected at least 3 requests, got 1
deduplication.preserves_uuid_and_timestamp_on_batch_retry
Expected at least 2 requests, got 1
error_handling.retries_on_408
Expected at least 2 requests, got 1
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 523ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 522ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 522ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 522ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 521ms |
| Request Payload.Groups Round Trip | ✅ | 522ms |
| Request Payload.Groups Default To Empty Object | ✅ | 522ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 521ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 522ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 522ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 516ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 509ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 527ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 523ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 624ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 625ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 525ms |
💡 Motivation and Context
The fork-curl consumer pipes JSON through shell
echobefore gzip compression. On shells that interpret backslash escapes, event properties containing newlines or literal backslashes can become invalid JSON or change value.Use
printf '%s'to preserve the JSON bytes. Add a loopback regression test that sends real curl requests in plain and gzip modes, then checks the decoded payload. It covers control characters, literal backslashes, quotes, percent signs and Unicode.Declare
ext-zlibin Composer's development dependencies and explicitly enable it in PHPUnit and coverage CI so the gzip regression always runs. SDK users do not acquire a new runtime dependency.This is the production fix found in #249, isolated on a fresh branch from
mainso it can be reviewed and released separately. It includes a patch change intent. The audit PR is unchanged and will need its duplicate change intent removed when it is synced after this fix merges.💚 How did you test it?
JsonException: Control character error.ConsumerFileTest::testSendandConsumerSocketTest::testProductionProblems. Existing warning and deprecation notices remain.git diff --checkpassed.a4aa9e6852fa7e485677490c2c23115387173cf4with no actionable findings.Local validation used PHP 8.5.10 on macOS. Linux and the other supported PHP versions were not run locally.
📝 Checklist
If releasing new changes
pnpm changeto generate a change intent file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Pi used file and shell tools to isolate the previously tested fix in a new worktree from
main. The test runs real curl and gzip processes against a local HTTP fixture reused from the audit, rather than mocking compression. The autoreview helper reviewed the committed branch. No session was published. Human review is required.