feat(gradle-plugin): default release mode to event - #739
Conversation
The proguard mapping uploads release-independent by default, and each event resolves its own release. Set posthog.releaseMode=symbol-set to keep stamping the release onto the mapping. 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 1 must fix, 1 should fix, 0 consider. Published 2 findings (view the review). |
|
PostHog Review alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏 |
| 'posthog-android-gradle-plugin': minor | ||
| --- | ||
|
|
||
| Default `posthog.releaseMode` to `event`. The proguard mapping is now uploaded release-independent, and each event resolves its own release from the `$app_namespace` / `$app_version` / `$app_build` the SDK already sends, so two releases that ship the same mapping no longer both report whichever release uploaded it first. Set `posthog.releaseMode=symbol-set`, or `POSTHOG_RELEASE_MODE=symbol-set`, to keep stamping the release onto the uploaded mapping. Event mode needs posthog-cli >= 0.12.0, and the release coordinates the build sends have to match the app's applicationId, versionName and versionCode. |
There was a problem hiding this comment.
The documented minimum CLI version rejects event mode
Why we think it's a valid issue
- Checked: the posthog-cli release history on npm, the CLI's own
CHANGELOG.mdfor 0.11.0 through 0.13.0, and the realx86_64-unknown-linux-gnurelease binaries for both 0.12.0 and 0.13.0. - Found: the CLI changelog assigns
--release-modeonproguard uploadto 0.13.0, not 0.12.0: "Add--release-modetoproguard upload, matchingsourcemap upload." Version 0.12.0 addsrelease resolveonly, which is unrelated. Version 0.11.0 adds--release-modeto the sourcemap commands (inject/upload/process), which is the likely source of the off-by-one. - Found: I ran the exact argument list the task builds against both binaries. Version 0.12.0 answers
error: unexpected argument '--release-mode' foundand exits with code 2; itsexp proguard upload --helphas no--release-modeentry. Version 0.13.0 lists--release-mode <RELEASE_MODE>in help and accepts it. - Found: this PR puts the flag on the default path.
Utils.kt:160now returnsPostHogReleaseMode.EVENTwhen nothing is configured,PostHogUploadProguardMappingsTask.kt:126defaultsregistertoEVENT, andgetArgumentsatPostHogUploadProguardMappingsTask.kt:111-114adds--release-modefor every mode except symbol-set. A build that configures nothing therefore passes the flag. - Found: the failure stops the build.
PostHogCliExecTaskextends Gradle'sExecand never setsisIgnoreExitValue, so the defaultfalseapplies and exit code 2 fails the task. The plugin also runs no version check at all —resolvePostHogCliExecutableinUtils.kt:170-239takes whicheverposthog-clisits on PATH or in a known install location. The documented minimum version is the only compatibility guard there is. - Impact: a user who reads the changeset, installs posthog-cli 0.12.0, and changes no Gradle property gets a failed
uploadPostHogProguardMappingstask with the misleading messageunexpected argument '--release-mode'. Before this PR the wrong number only affected people who opted into event mode; the new default applies it to every build. - Impact: the same wrong number already sits in the shipped
posthog-android-gradle-plugin/CHANGELOG.md:19, so the correction belongs in both places. - Priority: I keep
must_fix. The version window is narrow, because 0.13.0 shipped the same day as 0.12.0, so few users sit exactly on 0.12.0. The statement is still false, the documentation is the only compatibility guard, and the fix is one number. Only the version correction is required; the suggested test that runs the arguments against a downloaded CLI binary is heavier than this needs and is optional.
Issue description
The changeset states that event mode needs posthog-cli 0.12.0. However, version 0.12.0 rejects --release-mode as an unexpected argument. Version 0.13.0 accepts this flag. Users who follow this requirement will still get a failed upload task.
Suggested fix
Change the minimum version to 0.13.0. Add a compatibility test that runs the generated arguments against the stated minimum CLI version.
Prompt to fix with AI (copy-paste)
## Context
@.changeset/gradle-plugin-event-release-mode-default.md#L5
<issue_description>
The changeset states that event mode needs posthog-cli 0.12.0. However, version 0.12.0 rejects `--release-mode` as an unexpected argument. Version 0.13.0 accepts this flag. Users who follow this requirement will still get a failed upload task.
</issue_description>
<issue_validation>
- **Checked:** the posthog-cli release history on npm, the CLI's own `CHANGELOG.md` for 0.11.0 through 0.13.0, and the real `x86_64-unknown-linux-gnu` release binaries for both 0.12.0 and 0.13.0.
- **Found:** the CLI changelog assigns `--release-mode` on `proguard upload` to **0.13.0**, not 0.12.0: "Add `--release-mode` to `proguard upload`, matching `sourcemap upload`." Version 0.12.0 adds `release resolve` only, which is unrelated. Version 0.11.0 adds `--release-mode` to the *sourcemap* commands (`inject`/`upload`/`process`), which is the likely source of the off-by-one.
- **Found:** I ran the exact argument list the task builds against both binaries. Version 0.12.0 answers `error: unexpected argument '--release-mode' found` and exits with code **2**; its `exp proguard upload --help` has no `--release-mode` entry. Version 0.13.0 lists `--release-mode <RELEASE_MODE>` in help and accepts it.
- **Found:** this PR puts the flag on the default path. `Utils.kt:160` now returns `PostHogReleaseMode.EVENT` when nothing is configured, `PostHogUploadProguardMappingsTask.kt:126` defaults `register` to `EVENT`, and `getArguments` at `PostHogUploadProguardMappingsTask.kt:111-114` adds `--release-mode` for every mode except symbol-set. A build that configures nothing therefore passes the flag.
- **Found:** the failure stops the build. `PostHogCliExecTask` extends Gradle's `Exec` and never sets `isIgnoreExitValue`, so the default `false` applies and exit code 2 fails the task. The plugin also runs no version check at all — `resolvePostHogCliExecutable` in `Utils.kt:170-239` takes whichever `posthog-cli` sits on PATH or in a known install location. The documented minimum version is the only compatibility guard there is.
- **Impact:** a user who reads the changeset, installs posthog-cli 0.12.0, and changes no Gradle property gets a failed `uploadPostHogProguardMappings` task with the misleading message `unexpected argument '--release-mode'`. Before this PR the wrong number only affected people who opted into event mode; the new default applies it to every build.
- **Impact:** the same wrong number already sits in the shipped `posthog-android-gradle-plugin/CHANGELOG.md:19`, so the correction belongs in both places.
- **Priority:** I keep `must_fix`. The version window is narrow, because 0.13.0 shipped the same day as 0.12.0, so few users sit exactly on 0.12.0. The statement is still false, the documentation is the only compatibility guard, and the fix is one number. Only the version correction is required; the suggested test that runs the arguments against a downloaded CLI binary is heavier than this needs and is optional.
</issue_validation>
## Task
Investigate the issue and solve it
<potential_solution>
Change the minimum version to 0.13.0. Add a compatibility test that runs the generated arguments against the stated minimum CLI version.
</potential_solution>
| project.findProperty(POSTHOG_RELEASE_MODE_PROPERTY)?.toString()?.trim()?.takeIf { it.isNotEmpty() } | ||
| ?: environment[POSTHOG_RELEASE_MODE_ENV]?.trim()?.takeIf { it.isNotEmpty() } | ||
| ?: return PostHogReleaseMode.SYMBOL_SET | ||
| ?: return PostHogReleaseMode.EVENT |
There was a problem hiding this comment.
Event mode does not validate required release coordinates
Why we think it's a valid issue
- Checked: the argument builder in
PostHogUploadProguardMappingsTask.getArguments, the values the caller supplies, the SDK's runtime context builder, and the posthog-cli 0.13.0exp proguard upload --helptext from the real release binary. - Found: the CLI's own help confirms the git-derivation claim.
--release-versionreads: "The version of the project ... Required to have the uploaded chunks associated with a specific release. We will try to auto-derive this from git information if not provided".--release-namecarries the same sentence. So omitting the flag does not skip release creation — it creates a release under git-derived coordinates, typically a commit hash. - Found: the same help text states the matching requirement for the mode this PR now defaults to:
--release-mode event"leaves the mapping release-independent, and each event resolves its own release from the app version and namespace the SDK already sends, so the release coordinates have to match the app's". - Found: the blank path is real and unguarded.
PostHogAndroidGradlePlugin.kt:202passesprimaryOutput?.versionName?.map { it.orEmpty() }, which turns a nullversionNameinto"".PostHogUploadProguardMappingsTask.kt:100-103then drops the flag, becausetakeIf { it.isNotEmpty() }fails on the empty string. Nothing inresolvePostHogReleaseMode(Utils.kt:157-165) or the task checks the coordinates against the selected mode. - Found: the runtime side omits the property rather than sending a blank.
PostHogAndroidContext.kt:41-43guards withit.versionName?.let { name -> staticContext["\$app_version"] = name }, so a nullversionNameleaves$app_versionabsent from the event entirely. That explicit null check is also evidence the team treats a missingversionNameas reachable, not as a case the types rule out. - Impact: for an app with no
versionName, the upload creates a release keyed to a git commit hash while events carry no$app_versionat all. The two can never match, so in event mode the release is orphaned and errors resolve no release. The build prints no warning, so the failure is silent — the changeset states the coordinates "have to match the app's applicationId, versionName and versionCode", but nothing enforces it. - Impact: scope is narrower than the wording suggests, and the author should know both limits. Symbolication is unaffected: the symbol set is keyed by the per-variant map id that
InjectPostHogMetaPropertiesIntoAssetsTaskbakes into the assets andPostHogMetaPropertiesApplier.kt:47-51loads asreleaseIdentifier, so stack frames still resolve. What is lost is release attribution. Reachability is also limited, because this path only runs whenvariant.isMinifyEnabled(PostHogAndroidGradlePlugin.kt:60) and most shipped minified builds do set aversionName. - Impact: it still clears the bar. The mechanism is confirmed at every link, the change makes event mode the default for unconfigured builds, the degradation is silent, and the suggested guard — fail or warn when the coordinates are blank in event mode — is small and proportionate.
Issue description
Event mode now applies to every unconfigured build, but the upload task permits a blank releaseVersion and omits --release-version. Android permits a null or blank versionName. Runtime events then omit $app_version or send an empty value. The CLI can derive a different version from Git, so the event cannot resolve the uploaded release.
Suggested fix
In event mode, fail the upload when releaseName or releaseVersion is blank. Explain that the app must set applicationId and versionName. Alternatively, use one deterministic fallback in both the CLI upload and SDK events.
Prompt to fix with AI (copy-paste)
## Context
@posthog-android-gradle-plugin/src/main/kotlin/com/posthog/android/Utils.kt#L160
<issue_description>
Event mode now applies to every unconfigured build, but the upload task permits a blank releaseVersion and omits `--release-version`. Android permits a null or blank versionName. Runtime events then omit `$app_version` or send an empty value. The CLI can derive a different version from Git, so the event cannot resolve the uploaded release.
</issue_description>
<issue_validation>
- **Checked:** the argument builder in `PostHogUploadProguardMappingsTask.getArguments`, the values the caller supplies, the SDK's runtime context builder, and the posthog-cli 0.13.0 `exp proguard upload --help` text from the real release binary.
- **Found:** the CLI's own help confirms the git-derivation claim. `--release-version` reads: "The version of the project ... Required to have the uploaded chunks associated with a specific release. **We will try to auto-derive this from git information if not provided**". `--release-name` carries the same sentence. So omitting the flag does not skip release creation — it creates a release under git-derived coordinates, typically a commit hash.
- **Found:** the same help text states the matching requirement for the mode this PR now defaults to: `--release-mode event` "leaves the mapping release-independent, and each event resolves its own release from the app version and namespace the SDK already sends, **so the release coordinates have to match the app's**".
- **Found:** the blank path is real and unguarded. `PostHogAndroidGradlePlugin.kt:202` passes `primaryOutput?.versionName?.map { it.orEmpty() }`, which turns a null `versionName` into `""`. `PostHogUploadProguardMappingsTask.kt:100-103` then drops the flag, because `takeIf { it.isNotEmpty() }` fails on the empty string. Nothing in `resolvePostHogReleaseMode` (`Utils.kt:157-165`) or the task checks the coordinates against the selected mode.
- **Found:** the runtime side omits the property rather than sending a blank. `PostHogAndroidContext.kt:41-43` guards with `it.versionName?.let { name -> staticContext["\$app_version"] = name }`, so a null `versionName` leaves `$app_version` absent from the event entirely. That explicit null check is also evidence the team treats a missing `versionName` as reachable, not as a case the types rule out.
- **Impact:** for an app with no `versionName`, the upload creates a release keyed to a git commit hash while events carry no `$app_version` at all. The two can never match, so in event mode the release is orphaned and errors resolve no release. The build prints no warning, so the failure is silent — the changeset states the coordinates "have to match the app's applicationId, versionName and versionCode", but nothing enforces it.
- **Impact:** scope is narrower than the wording suggests, and the author should know both limits. Symbolication is unaffected: the symbol set is keyed by the per-variant map id that `InjectPostHogMetaPropertiesIntoAssetsTask` bakes into the assets and `PostHogMetaPropertiesApplier.kt:47-51` loads as `releaseIdentifier`, so stack frames still resolve. What is lost is release attribution. Reachability is also limited, because this path only runs when `variant.isMinifyEnabled` (`PostHogAndroidGradlePlugin.kt:60`) and most shipped minified builds do set a `versionName`.
- **Impact:** it still clears the bar. The mechanism is confirmed at every link, the change makes event mode the default for unconfigured builds, the degradation is silent, and the suggested guard — fail or warn when the coordinates are blank in event mode — is small and proportionate.
</issue_validation>
## Task
Investigate the issue and solve it
<potential_solution>
In event mode, fail the upload when releaseName or releaseVersion is blank. Explain that the app must set applicationId and versionName. Alternatively, use one deterministic fallback in both the CLI upload and SDK events.
</potential_solution>
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
posthog.releaseModedefaults toevent.$app_namespace,$app_versionand$app_buildthe SDK sends.posthog.releaseMode=symbol-setkeeps the old behavior.POSTHOG_RELEASE_MODE=symbol-setdoes the same.PostHogUploadProguardMappingsTask.registertakes the same default.How did you test this code?
./gradlew :posthog-android-gradle-plugin:testand./gradlew spotlessCheck detekt.PostHogReleaseModeTestis new.resolvePostHogReleaseModehad no tests.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Written with Claude Code (Opus 5).
The test helper builds its
Projectwithoutapply {}.Projecthas its ownapply(), so the scope function resolves to Gradle's method.