Repository navigation
feat(symbols): zero-config Android uploads, and gzip what an upload sends - #767
Merged
Merged
Conversation
`ldcli symbols upload --type android` needed a --path pointing at a directory holding mapping.txt and an --app-version matching what the app reports, so every project had to add build script code to stage the mapping somewhere and plumb its version into CI. The Android Gradle Plugin already writes both: R8's mapping at <module>/build/outputs/mapping/<variant>/mapping.txt, and the version it packaged in output-metadata.json beside the APK. Read them, and the command works from an Android project root with no flags and no build script at all. Several obfuscated variants is an error naming them rather than a guess, since only one of them is the build being shipped. An Android mapping is now also stored as mapping.txt however deep it was found. Symbolication reads it at <lane>/mapping.txt, so a mapping uploaded from a nested path was previously keyed somewhere nothing looks. Co-authored-by: Cursor <cursoragent@cursor.com>
A build that stamps a content-derived symbols id into assets/ld_symbols_id.txt had to also stage a mapping.txt.symbolsid sidecar next to a copy of the mapping, purely so the upload could be keyed by the same id the app reports. The packaged APK/AAB already carries that asset, and it is the app that will run: what it carries is exactly what will be reported. Reading it there needs nothing handed over by the build and leaves no way for the two to disagree, so the staging step goes away and a stamped build uploads on the Symbols Id Lane with no flags. An id that does not match the 32-hex-char shape the SDK reports is ignored rather than keyed on, since it names a lane nothing would ever ask for. Co-authored-by: Cursor <cursoragent@cursor.com>
A release R8 mapping is tens of megabytes of text and every build pushes it again: the e2e app's is 61.3 MB, which gzips to 5.0 MB in under half a second. React Native source maps and Apple symbol maps compress on the same order. Each object is marked Content-Encoding: gzip, so it stays self-describing — storage returns the header on read, an HTTP client inflates it in transit, and the backend inflates whatever still arrives compressed. Artifacts held in memory are compressed before an upload URL is asked for rather than at the point of sending, because the digest that proves an object is already stored is compared against the ETag of the stored bytes; hashing the artifact instead would never match and every source bundle would be re-sent. Files are compressed as they are read, streamed through the compressor so a large mapping is never held in memory whole. Anything that comes out of gzip no smaller is sent as it is, so a .srcbundle, which already gzips its own entries, is not wrapped a second time. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1b66af8. Configure here.
5 tasks done
Vadman97
approved these changes
Jul 31, 2026
abelonogov-ld
added a commit
that referenced
this pull request
Jul 31, 2026
#767 was squash-merged, so main carries as one commit what this branch has as three, and git saw both sides as having changed cmd/symbols/upload.go. The squash is byte-identical to what this branch started from and main has nothing else new, so the branch's own version stands and the merge changes no file. Co-authored-by: Cursor <cursoragent@cursor.com>
social4hyq
pushed a commit
to social4hyq/homebrew-core
that referenced
this pull request
Sep 20, 2026
ldcli 3.8.0 Created-by: HarmonybrewBot Commit-by: HarmonybrewBot Merged-by: HarmonybrewBot Description: Created by `brew bump` --- Created with `brew bump-formula-pr`.<details> <summary>release notes</summary> <pre>## [3.8.0](launchdarkly/ldcli@v3.7.0...v3.8.0) (2026-08-01) ### Features * **symbols:** build the R8 mapping index in the CLI ([#768](launchdarkly/ldcli#768)) ([8693730](launchdarkly/ldcli@8693730)) * **symbols:** zero-config Android uploads, and gzip what an upload sends ([#767](launchdarkly/ldcli#767)) ([edcf301](launchdarkly/ldcli@edcf301)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). <!-- CURSOR_SUMMARY --></pre> <p>View the full release notes at <a href="https://github.com/launchdarkly/ldcli/releases/tag/v3.8.0">https://github.com/launchdarkly/ldcli/releases/tag/v3.8.0</a>.</p> </details> <hr> See merge request: Harmonybrew/homebrew-core!15651
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Three changes that together let
ldcli symbols upload --type androidrun from an Android project root with no flags, and cut what every upload sends.Find the mapping and app version in the build. The command previously needed a
--pathpointing at a directory holdingmapping.txtand an--app-versionmatching what the app reports, so every project had to add build-script code to stage the mapping and plumb its version into CI. The Android Gradle Plugin already writes both — R8's mapping at<module>/build/outputs/mapping/<variant>/mapping.txt, and the packaged version inoutput-metadata.jsonbeside the APK. Several obfuscated variants is an error naming them rather than a guess, since only one is the build being shipped. An Android mapping is also now stored asmapping.txthowever deep it was found; symbolication reads it at<lane>/mapping.txt, so a mapping uploaded from a nested path used to be keyed where nothing looks.Read the symbols id out of the packaged app. A build that stamps a content-derived id into
assets/ld_symbols_id.txthad to also stage amapping.txt.symbolsidsidecar next to a copy of the mapping, purely so the upload could be keyed by the id the app reports. The APK/AAB already carries that asset, and it is the app that will run, so reading it there needs nothing handed over by the build and leaves no way for the two to disagree. An id that doesn't match the 32-hex-char shape the SDK reports is ignored rather than keyed on.Gzip what an upload sends. The e2e app's mapping is 61.3 MB and gzips to 5.0 MB in under half a second; React Native and Apple artifacts compress on the same order. Objects are marked
Content-Encoding: gzipso they stay self-describing. In-memory artifacts are compressed before an upload URL is requested, because the digest that proves an object is already stored is compared against the ETag of the stored bytes — hashing the uncompressed artifact would never match and every source bundle would be re-sent. Files are streamed through the compressor so a large mapping is never held in memory whole. Anything gzip doesn't shrink is sent as-is, so a.srcbundleisn't wrapped twice.Ordering
The backend read side must ship first: https://github.com/launchdarkly/observability/pull/1611. Until it is deployed, a compressed mapping would reach the parser as bytes it cannot read.
Test plan
go test ./cmd/symbols/Made with Cursor