Skip to content

feat: upload dsym symbol sets release-independent by default - #789

Closed
ablaszkiewicz wants to merge 2 commits into
mainfrom
feat/event-release-mode-default
Closed

feat: upload dsym symbol sets release-independent by default#789
ablaszkiewicz wants to merge 2 commits into
mainfrom
feat/event-release-mode-default

Conversation

@ablaszkiewicz

@ablaszkiewicz ablaszkiewicz commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Related PRs

Event mode is live today. Each build must ask for it. These PRs make it the default. The modes themselves do not change.

Problem

  • Two iOS releases that ship the same dSYM report their crashes on one release.
  • The release that uploads the symbol set first takes them.

Changes

  • upload-symbols.sh passes dsym upload --no-release-bind by default.
  • The script still creates the release. Each crash resolves its own release from the $app_version, $app_namespace and $app_build the SDK sends.
  • POSTHOG_NO_RELEASE_BIND=0 keeps the old behavior. false and no do the same.
  • The posthog-cli floor moves to 0.10.0 for a default build. The script checks the version before it uploads.

Land PostHog/posthog-js#4706 with this PR. A React Native dSYM phase must name the opt-out once this default changes.

How did you test this code?

  • bash build-tools/upload-symbols.test.sh.
  • Four new tests. POSTHOG_NO_RELEASE_BIND had no tests.
  • The new default test fails against the previous script.
  • Not run: an Xcode build.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Written with Claude Code (Opus 5). Skills invoked: /writing-pr-descriptions.

This repository keeps a boolean, because dsym upload takes --no-release-bind and not --release-mode. The other repositories use the symbol-set and event names.

The opt-out is a case statement and not a = "1" test. An unknown value keeps the new default.

The release is still created, and each crash resolves its own release from
the app version and namespace the SDK sends. Set POSTHOG_NO_RELEASE_BIND=0
to keep binding the symbol sets to the release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jg6r6rFUBnowrXssZyU7yR
@posthog

posthog Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

🦔 PostHog Review reviewed this pull request

Found 0 must fix, 0 should fix, 2 consider.

Published 2 findings (view the review).

@posthog

posthog Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

PostHog Review alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏

@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

Found 2 consider.

Comment on lines +257 to 261
# Upload the symbol sets without binding them to the release. The release is still created, and
# the server resolves it from the $app_version / $app_namespace / $app_build the SDK sends on every
# event, so nothing is written into the built bundle.
if [ "${POSTHOG_NO_RELEASE_BIND_ENABLED}" = "1" ]; then
CLI_ARGS+=(--no-release-bind)

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.

Embedded targets cannot resolve a release

consider bug

Why we think it's a valid issue
  • Checked: The CLI arguments the script assembles at build-tools/upload-symbols.sh:221-248, how $app_namespace is produced in the SDK, and whether this SDK is expected to run inside an app extension process.
  • Found: The upload is folder-wide but the release is singular. CLI_ARGS=(--directory "${DWARF_DSYM_FOLDER_PATH}") at build-tools/upload-symbols.sh:222 covers every dSYM the build produced, including each embedded extension's. Against that, --release-name "${PRODUCT_BUNDLE_IDENTIFIER}" at build-tools/upload-symbols.sh:236-238 names exactly one bundle identifier — the target that runs the build phase. --main-dsym at line 226 only marks which dSYM to read versions from; it does not narrow the upload. So one release covers many dSYMs, and that asymmetry is visible in this file without any assumption about the CLI.
  • Found: The namespace an extension reports genuinely differs. $app_namespace is set from getBundleIdentifier() at PostHog/PostHogContext.swift:46, which resolves from Bundle.main in PostHog/PostHogStorage.swift:71-77. Inside an extension process Bundle.main is the .appex, so the value is the extension's own identifier, not PRODUCT_BUNDLE_IDENTIFIER.
  • Found: Running in an extension is a supported, expected configuration for this SDK, not a hypothetical. isExtension() at PostHog/PostHogStorage.swift:84-86 detects the .appex process explicitly, and the app-group container migration documented at PostHog/PostHogStorage.swift:88-95 exists to share PostHog state between the app and its extensions. So the trigger needs no user misconfiguration — only ordinary iOS app architecture.
  • Found: The gap follows from the PR's own stated design rather than an invented premise. The comment this change adds at build-tools/upload-symbols.sh:257-259 says the server resolves the release from $app_version / $app_namespace / $app_build. For an extension crash that lookup keys on an identifier for which this upload created no release, and the change removes the binding that previously associated every uploaded dSYM with the one release that was created.
  • Impact: Confirmed as a real gap opened by the default flip, affecting apps that embed an extension running the SDK. The stack trace still symbolicates, because the extension's symbol set is still uploaded and matched by debug identifier; what degrades is release attribution on those crashes.
  • Priority: Lowered to consider. The terminal step — that the server ends up omitting $exception_release — is server-side behavior, and no PostHog server or posthog-cli checkout exists in this workspace to confirm it. The consequence is missing metadata rather than broken symbolication or lost data, the affected population is the subset of apps with SDK-initialized extensions, and the suggested remedy (a release per executable target) needs CLI support this script cannot supply alone. Worth telling the author so the extension case is handled or documented, but below the bar for should_fix.
Issue description

The CLI scans every dSYM but creates only the main target's release. This flag removes the release ID from every scanned dSYM. An extension sends its own $app_namespace, so the server looks for a release that the upload never created. The symbol set has no bound release fallback. These crashes therefore omit $exception_release.

Suggested fix

Create a release for each executable target before uploading its dSYM without a binding. Otherwise, keep non-main dSYMs bound. Add a test with two dSYMs and distinct bundle identifiers.

Prompt to fix with AI (copy-paste)
## Context
@build-tools/upload-symbols.sh#L257-261

<issue_description>
The CLI scans every dSYM but creates only the main target's release. This flag removes the release ID from every scanned dSYM. An extension sends its own `$app_namespace`, so the server looks for a release that the upload never created. The symbol set has no bound release fallback. These crashes therefore omit `$exception_release`.
</issue_description>

<issue_validation>
- **Checked:** The CLI arguments the script assembles at `build-tools/upload-symbols.sh:221-248`, how `$app_namespace` is produced in the SDK, and whether this SDK is expected to run inside an app extension process.
- **Found:** The upload is folder-wide but the release is singular. `CLI_ARGS=(--directory "${DWARF_DSYM_FOLDER_PATH}")` at `build-tools/upload-symbols.sh:222` covers every dSYM the build produced, including each embedded extension's. Against that, `--release-name "${PRODUCT_BUNDLE_IDENTIFIER}"` at `build-tools/upload-symbols.sh:236-238` names exactly one bundle identifier — the target that runs the build phase. `--main-dsym` at line 226 only marks which dSYM to read versions from; it does not narrow the upload. So one release covers many dSYMs, and that asymmetry is visible in this file without any assumption about the CLI.
- **Found:** The namespace an extension reports genuinely differs. `$app_namespace` is set from `getBundleIdentifier()` at `PostHog/PostHogContext.swift:46`, which resolves from `Bundle.main` in `PostHog/PostHogStorage.swift:71-77`. Inside an extension process `Bundle.main` is the `.appex`, so the value is the extension's own identifier, not `PRODUCT_BUNDLE_IDENTIFIER`.
- **Found:** Running in an extension is a supported, expected configuration for this SDK, not a hypothetical. `isExtension()` at `PostHog/PostHogStorage.swift:84-86` detects the `.appex` process explicitly, and the app-group container migration documented at `PostHog/PostHogStorage.swift:88-95` exists to share PostHog state between the app and its extensions. So the trigger needs no user misconfiguration — only ordinary iOS app architecture.
- **Found:** The gap follows from the PR's own stated design rather than an invented premise. The comment this change adds at `build-tools/upload-symbols.sh:257-259` says the server resolves the release from `$app_version` / `$app_namespace` / `$app_build`. For an extension crash that lookup keys on an identifier for which this upload created no release, and the change removes the binding that previously associated every uploaded dSYM with the one release that was created.
- **Impact:** Confirmed as a real gap opened by the default flip, affecting apps that embed an extension running the SDK. The stack trace still symbolicates, because the extension's symbol set is still uploaded and matched by debug identifier; what degrades is release attribution on those crashes.
- **Priority:** Lowered to `consider`. The terminal step — that the server ends up omitting `$exception_release` — is server-side behavior, and no PostHog server or posthog-cli checkout exists in this workspace to confirm it. The consequence is missing metadata rather than broken symbolication or lost data, the affected population is the subset of apps with SDK-initialized extensions, and the suggested remedy (a release per executable target) needs CLI support this script cannot supply alone. Worth telling the author so the extension case is handled or documented, but below the bar for `should_fix`.
</issue_validation>

## Task
Investigate the issue and solve it

<potential_solution>
Create a release for each executable target before uploading its dSYM without a binding. Otherwise, keep non-main dSYMs bound. Add a test with two dSYMs and distinct bundle identifiers.
</potential_solution>

Comment thread build-tools/upload-symbols.sh
The script sent CFBundleVersion as written, so 001 created the release
version+001. The SDK reports $app_build as a number, so every event
carried 1. The release matched no event, and an unbound symbol set has no
binding to fall back on.

A build that is not all digits stays as written, which is what the SDK
does with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jg6r6rFUBnowrXssZyU7yR
@ablaszkiewicz

Copy link
Copy Markdown
Contributor Author

Closing this. We are removing event release mode from this package instead of defaulting to it.

Event mode only helps when two releases ship a byte-identical artifact. The symbol id is a content hash on every platform, so an ordinary feature release already gets its own symbol set and never collides. The case that does collide is a React Native release that changes only JavaScript, where the native artifact repeats.

Against that narrow benefit, the mobile paths carry real cost. A dSYM upload covers every embedded target but creates one release, so an extension crash resolves no release once the binding is gone.

A replacement PR follows that removes the mode from this package. The flag was experimental and undocumented, so the removal is direct.

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