feat: upload dsym symbol sets release-independent by default - #789
feat: upload dsym symbol sets release-independent by default#789ablaszkiewicz wants to merge 2 commits into
Conversation
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 Review reviewed this pull requestFound 0 must fix, 0 should fix, 2 consider. Published 2 findings (view the review). |
|
PostHog Review alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏 |
| # 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) |
There was a problem hiding this comment.
Embedded targets cannot resolve a release
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_namespaceis 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}")atbuild-tools/upload-symbols.sh:222covers every dSYM the build produced, including each embedded extension's. Against that,--release-name "${PRODUCT_BUNDLE_IDENTIFIER}"atbuild-tools/upload-symbols.sh:236-238names exactly one bundle identifier — the target that runs the build phase.--main-dsymat 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_namespaceis set fromgetBundleIdentifier()atPostHog/PostHogContext.swift:46, which resolves fromBundle.maininPostHog/PostHogStorage.swift:71-77. Inside an extension processBundle.mainis the.appex, so the value is the extension's own identifier, notPRODUCT_BUNDLE_IDENTIFIER. - Found: Running in an extension is a supported, expected configuration for this SDK, not a hypothetical.
isExtension()atPostHog/PostHogStorage.swift:84-86detects the.appexprocess explicitly, and the app-group container migration documented atPostHog/PostHogStorage.swift:88-95exists 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-259says 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 forshould_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>
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
|
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. |
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
Changes
upload-symbols.shpassesdsym upload --no-release-bindby default.$app_version,$app_namespaceand$app_buildthe SDK sends.POSTHOG_NO_RELEASE_BIND=0keeps the old behavior.falseandnodo the same.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.POSTHOG_NO_RELEASE_BINDhad no tests.🤖 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 uploadtakes--no-release-bindand not--release-mode. The other repositories use thesymbol-setandeventnames.The opt-out is a
casestatement and not a= "1"test. An unknown value keeps the new default.