Skip to content

feat: send error tracking stack frames in canonical bottom-up order#603

Draft
cat-ph wants to merge 1 commit into
mainfrom
cat/canonical-frame-order
Draft

feat: send error tracking stack frames in canonical bottom-up order#603
cat-ph wants to merge 1 commit into
mainfrom
cat/canonical-frame-order

Conversation

@cat-ph

@cat-ph cat-ph commented Jul 3, 2026

Copy link
Copy Markdown

💡 Motivation and Context

Part of cross-SDK error tracking standardization: PostHog/sdk-specs#11.

The canonical wire order for $exception_list[].stacktrace.frames is bottom-up: frames[0] is the outermost/entry point and the last frame is the crash site. Today the shared exception coercer emits Java's native innermost-first order (crash site first), which is the opposite of the canonical convention.

This PR flips the frame order to match the spec.

🔧 The change

ThrowableCoercer.fromThrowableToPostHogProperties now walks throwable.stackTrace in reverse when building the frames list, so the emitted order is entry-point-first / crash-site-last.

The $exception_list ordering itself is unchanged (already canonical: [0] = outermost exception, built by walking .cause). Only the per-exception frame order flips.

Because this coercer is shared, the change covers both shipped $libs:

  • posthog-android (Android SDK)
  • posthog-server (Java server SDK)

💚 How did you test it?

  • Updated the existing frame-order assertions in posthog/src/test/java/com/posthog/PostHogTest.kt.
  • Added explicit regression assertions: the emitted frames are the reverse of the native stackTrace — first frame = entry point, last frame = crash site, and the crash-site (last) frame is the in-app method that threw.
  • ./gradlew spotlessCheck :posthog:test passes (748 tests).

🤝 Coordination

This is a BREAKING wire-order change. Merge and release only after the ingestion pipeline's frame-order normalization gate (cymbal) is live, so older-order and newer-order events are both normalized correctly server-side.

The release must be a MINOR version bump for both posthog-android and posthog-server so the pipeline can gate normalization on $lib_version. The changeset in this PR declares minor bumps for posthog, posthog-android, and posthog-server.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • Ran the changeset (.changeset/canonical-frame-order.md, minor bumps).
  • Added the "release" label to the PR (hold until the normalization gate is live).

@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown

Reviews (1): Last reviewed commit: "feat: send error tracking stack frames i..." | Re-trigger Greptile

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

posthog-android Compliance Report

Date: 2026-07-03 00:13:26 UTC
Duration: 118262ms

✅ All Tests Passed!

46/46 tests passed


Capture Tests

29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields 335ms
Format Validation.Event Has Uuid 27ms
Format Validation.Event Has Lib Properties 31ms
Format Validation.Distinct Id Is String 27ms
Format Validation.Token Is Present 24ms
Format Validation.Custom Properties Preserved 23ms
Format Validation.Event Has Timestamp 25ms
Retry Behavior.Retries On 503 7026ms
Retry Behavior.Does Not Retry On 400 4022ms
Retry Behavior.Does Not Retry On 401 4022ms
Retry Behavior.Respects Retry After Header 7026ms
Retry Behavior.Implements Backoff 17019ms
Retry Behavior.Retries On 500 7018ms
Retry Behavior.Retries On 502 7019ms
Retry Behavior.Retries On 504 7018ms
Retry Behavior.Max Retries Respected 17029ms
Deduplication.Generates Unique Uuids 37ms
Deduplication.Preserves Uuid On Retry 7016ms
Deduplication.Preserves Uuid And Timestamp On Retry 12028ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 7020ms
Deduplication.No Duplicate Events In Batch 32ms
Deduplication.Different Events Have Different Uuids 24ms
Compression.Sends Gzip When Enabled 18ms
Batch Format.Uses Proper Batch Structure 16ms
Batch Format.Flush With No Events Sends Nothing 11ms
Batch Format.Multiple Events Batched Together 36ms
Error Handling.Does Not Retry On 403 4018ms
Error Handling.Does Not Retry On 413 4021ms
Error Handling.Retries On 408 5025ms

Feature_Flags Tests

17/17 tests passed

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

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.

1 participant