Skip to content

feat(auto-install): Fail the build on OpenTelemetry version downgrades#1350

Open
adinauer wants to merge 15 commits into
side-quest/sagp-bom-version-use-skill-createfrom
side-quest/sagp-otel-version-check
Open

feat(auto-install): Fail the build on OpenTelemetry version downgrades#1350
adinauer wants to merge 15 commits into
side-quest/sagp-bom-version-use-skill-createfrom
side-quest/sagp-otel-version-check

Conversation

@adinauer

@adinauer adinauer commented Jul 2, 2026

Copy link
Copy Markdown
Member

Adds a JVM-only verifySentryOpenTelemetryVersions Gradle task that fails the build when the OpenTelemetry versions resolved on the runtime classpath are downgraded below what Sentry's sentry-opentelemetry-* artifacts require.

Why

Sentry's OpenTelemetry artifacts are built against a specific set of OpenTelemetry versions. When another dependency management mechanism — most commonly Spring Boot's io.spring.dependency-management — forces OpenTelemetry below those versions (which happens silently, without the user declaring any OTel version), running against the mismatched versions throws ClassNotFoundException / NoSuchMethodError at runtime. This surfaces the problem at build time with actionable guidance instead.

How it works

  • Walks the resolved runtimeClasspath graph and, for every OpenTelemetry module a sentry-opentelemetry-* artifact depends on, compares the requested vs. resolved version. Gradle preserves the originally-requested version on each edge even when a resolution rule (e.g. io.spring.dependency-management) forces it lower, so the downgrade is detectable.
  • The failure message adapts to the setup: it suggests importing the Sentry OpenTelemetry BOM through io.spring.dependency-management's imports { mavenBom(...) } when that plugin is applied (a platform() constraint loses to it), otherwise a plain platform(...) dependency.
  • Gated by a cheap declared-dependency check so it's skipped (no runtime classpath resolution) for projects that don't use Sentry OpenTelemetry.
  • Opt out via sentry.autoInstallation.verifyOpenTelemetryVersions = false (default true).

Context

Stacked on #1349 (base branch), which adds sentry-opentelemetry-bom awareness to auto-install.

This replaces an earlier approach that auto-imported the BOM: that only works with Gradle-native BOM support and is silently overridden by io.spring.dependency-management, so detecting-and-failing is the more honest behavior.

Open items from SDK-team review

  • Docs URL in the failure message is a placeholder (.../platforms/java/opentelemetry/troubleshooting/) and must resolve to a live page before release.
  • Default-on behavior is a product call: defaulting a build-breaking check to true will surprise existing Spring Boot users on upgrade. Either keep default-on with a clearly-flagged behavioral-change changelog entry, or ship opt-in first and flip later based on telemetry.

adinauer and others added 2 commits July 2, 2026 11:40
Sentry's sentry-opentelemetry-* artifacts are built against specific
OpenTelemetry versions. When another dependency management mechanism -
most commonly Spring Boot's io.spring.dependency-management - forces
OpenTelemetry below the version Sentry's integration requires, running
against those downgraded versions can cause ClassNotFoundException /
NoSuchMethodError at runtime.

Add a JVM-only verifySentryOpenTelemetryVersions task that walks the
resolved runtimeClasspath graph and fails the build when an OpenTelemetry
module a sentry-opentelemetry-* artifact depends on resolves lower than
the requested version. The failure message adapts to the project setup:
it suggests importing the Sentry OpenTelemetry BOM via
io.spring.dependency-management when that plugin is applied, otherwise a
platform() dependency.

The check is gated by a cheap declared-dependency lookup so it is skipped
(no runtime classpath resolution) for projects that don't use Sentry
OpenTelemetry, and can be disabled via
sentry.autoInstallation.verifyOpenTelemetryVersions = false.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 45dd618

adinauer and others added 2 commits July 2, 2026 11:58
…heck

Remove comments that restated self-explanatory code (the verifyEnabled
property KDoc and two test comments that duplicated their assertions).

Co-Authored-By: Claude <noreply@anthropic.com>
The springDependencyManagementApplied and hasSentryOpenTelemetryDependency
property names are self-explanatory, and the rationale already lives at the
usage sites (buildMessage and register).

Co-Authored-By: Claude <noreply@anthropic.com>
companion object {
// TODO: finalize the troubleshooting docs URL before release.
internal const val OPENTELEMETRY_VERSION_MISMATCH_DOCS_URL =
"https://docs.sentry.io/platforms/java/opentelemetry/troubleshooting/"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO docs don't exist yet

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread CHANGELOG.md Outdated
Co-authored-by: Alexander Dinauer <adinauer@users.noreply.github.com>
@adinauer adinauer marked this pull request as ready for review July 2, 2026 10:19

@runningcode runningcode 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.

Thanks for putting this together. Overall looks good. I left some comments, let me know if you'd like to discuss any of these.

* `NoSuchMethodError` at runtime. We detect that downgrade here and fail fast with actionable
* guidance instead of letting it blow up at runtime.
*/
abstract class SentryOpenTelemetryVersionCheckTask : DefaultTask() {

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.

This task is missing either a @DisableCachingByDefault or a @CacheableTask annotation. Check the build failure for details.

In this case since we have no outputs it should probably be @DisableCachingByDefault. Otherwise we can write a simple text file in a task specific output directory and make this task cacheable.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipping the output file for now. We can always add that later if needed.

import org.gradle.util.GradleVersion
import org.junit.Test

class SentryOpenTelemetryVersionCheckTest :

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.

Can we add a configuration cache test?

* `sentry-opentelemetry-*` artifact depends on, the ones whose resolved version is lower than
* the version Sentry requested.
*/
internal fun collectDowngrades(root: ResolvedComponentResult): List<VersionMismatch> {

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.

do you think it would be possible to split this pure logic from the gradle task and then unit test that separately?
I'm not saying we need to add more tests, but maybe we can move some of the tests from integration tests to just be plain unit tests which are significantly faster than integration tests that spin up a whole gradle build

assertTrue {
"OpenTelemetry was downgraded below the version its integration requires" in result.output
}
assertTrue { "io.opentelemetry:opentelemetry-sdk" in result.output }

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.

Can we use Google Truth instead of kotlin.test error messages and debugging is a lot easier especially when matching strings.

private const val OTEL_GROUP = "io.opentelemetry"
private const val SPRING_DEPENDENCY_MANAGEMENT_PLUGIN_ID = "io.spring.dependency-management"

internal data class VersionMismatch(

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.

nit: In this case we only ever hold downgrades so this might be a better name:

Suggested change
internal data class VersionMismatch(
internal data class VersionDowngrade(

|
| dependencyManagement {
| imports {
| mavenBom("io.sentry:sentry-opentelemetry-bom:<sentryVersion>")

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.

would it make sense to fill in from the value in AutoInstallState.sentryVersion ?

and also for 174?

internal data class VersionMismatch(
val module: String,
val requested: String,
val resolved: String,

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.

We can get more information from Gradle's dependency management engine in order to improve the error message.

Suggested change
val resolved: String,
val resolved: String,
val requestedBy: String,
val reason: String,

Set requestedBy from component.moduleVersion (which you already know is the Sentry OTel artifact), and pull reason from the selection reason of the selected node:

private fun ResolvedComponentResult.downgradeReason(): String? =
  selectionReason.descriptions
    .lastOrNull {
      it.cause == ComponentSelectionCause.CONSTRAINT ||
        it.cause == ComponentSelectionCause.FORCED ||
        it.cause == ComponentSelectionCause.CONFLICT_RESOLUTION
    }
    ?.description

Let me know if you need more info here!

|Sentry detected that OpenTelemetry was downgraded below the version its integration requires.
|
|The Sentry OpenTelemetry integration was built against specific OpenTelemetry versions,
|but the following were downgraded by another dependency management mechanism:

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.

You can also add that users can run this command or check a build scan for more details:

./gradlew :app:dependencyInsight --configuration runtimeClasspath --dependency io.opentelemetry:opentelemetry-sdk

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid of getting the command wrong and adding even more confusion. Also not fully sure if this works for Spring Boot so I'd rather skip this for now.

adinauer and others added 2 commits July 6, 2026 14:51
Co-authored-by: Nelson Osacky <nelson@osacky.com>
Declare the verification task as non-cacheable because it only validates the resolved dependency graph and has no outputs.

Add integration coverage to assert the task stores and reuses Gradle's configuration cache.

Co-Authored-By: Claude <noreply@anthropic.com>
)
// Gate compilation so any build that compiles, packages, runs or tests (assemble, bootJar,
// bootRun, check, ...) fails fast on an OpenTelemetry version mismatch.
project.tasks.named("classes").configure { it.dependsOn(verifyOpenTelemetryVersionsTask) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verify hook skips test builds

High Severity

The OpenTelemetry verify task is attached only as a dependency of classes, but the Java plugin’s test task does not depend on classes (it reaches main compilation via compileTestJavacompileJava). Typical ./gradlew test or ./gradlew check runs can therefore compile and execute tests without ever running verifySentryOpenTelemetryVersions, so OTel downgrades may not fail the build despite the stated goal.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e4f8f9b. Configure here.

adinauer and others added 6 commits July 6, 2026 15:07
Remove the outdated placeholder comment now that the troubleshooting docs PR exists.

Co-Authored-By: Claude <noreply@anthropic.com>
Rename the internal version check data class to describe the downgrade it represents.

Co-Authored-By: Claude <noreply@anthropic.com>
Include the Sentry OpenTelemetry artifact requesting each downgraded module and Gradle's selection reason when available.

This gives users more context about which integration was affected and why Gradle resolved the lower OpenTelemetry version.

Co-Authored-By: Claude <noreply@anthropic.com>
Use the resolved Sentry OpenTelemetry artifact version in the suggested BOM coordinates instead of a placeholder.

This makes the failure message copy-pasteable and matches the version whose OpenTelemetry requirements were checked.

Co-Authored-By: Claude <noreply@anthropic.com>
Move OpenTelemetry downgrade detection and message formatting out of the Gradle task so the task only handles wiring and execution.

Add focused unit coverage for artifact detection and failure message formatting.

Co-Authored-By: Claude <noreply@anthropic.com>
Convert OpenTelemetry version check integration assertions from kotlin.test to Google Truth for clearer string assertion failures.

Co-Authored-By: Claude <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6a5a723. Configure here.

@adinauer adinauer requested a review from runningcode July 7, 2026 04:13
Clean up the verifyOpenTelemetryVersions property documentation so formatting checks pass and the behavior is described clearly.

Co-Authored-By: Claude <noreply@anthropic.com>

@runningcode runningcode 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.

Looks great! Thanks for the cleanup and adding the configuration cache test.

I realized we should separate integrationTests from unitTests into separate source directories/tasks but that is an already existing organization issue.

* what the Sentry OpenTelemetry integration requires, failing the build if any were downgraded.
* Defaults to `true`.
*/
val verifyOpenTelemetryVersions: Property<Boolean> =

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this isn't tied to auto install, the option shouldn't live under auto install.

Expose the OpenTelemetry downgrade check directly on the Sentry plugin extension instead of under autoInstallation. This reflects that the check validates dependency resolution independently of auto-installation.

Refs #1350

Co-Authored-By: Claude <noreply@anthropic.com>
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.

2 participants