From fc9d95b1694157441c716d5609ca3b82b620f03d Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Wed, 12 Aug 2026 16:28:13 +0200 Subject: [PATCH 1/2] docs(agents): Forbid introducing new catch Throwable Add an "Exception Handling" section to AGENTS.md telling agents to catch the narrowest exception type the guarded code can throw, and to never add a new catch (Throwable). The repo already has ~355 such catches in main sources; they are legacy and should not be read as a precedent. A broad catch swallows OutOfMemoryError, StackOverflowError, ThreadDeath and LinkageError, and hides bugs in our own code behind a log line. The "never crash the host app" requirement is what usually motivates these catches, so the section points at ExceptionUtils.rethrowIfFatal as the way to satisfy it, and calls out the two cases that need care: probing an optional compileOnly dependency via a LinkageError subclass, and not swallowing InterruptedException. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index f785961ece..29d5b5cbfc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -154,6 +154,41 @@ The repository is organized into multiple modules: - **Formatting**: Enforced via Spotless - always run `./gradlew spotlessApply` before committing - **API Compatibility**: Binary compatibility is enforced - run `./gradlew apiDump` after API changes +### Exception Handling + +**Never introduce a new `catch (Throwable)`.** Catch the narrowest type the guarded code can +actually throw. The repository still contains many pre-existing broad catches; they are legacy, +not a precedent to follow. + +A broad catch swallows `OutOfMemoryError`, `StackOverflowError`, `ThreadDeath` and `LinkageError` — +conditions the JVM/ART cannot recover from and that leave the process in an undefined state — and +it hides real bugs in our own code behind a log line. + +"The SDK must never crash the host application" is not a reason to catch `Throwable`. That goal is +served by `io.sentry.util.ExceptionUtils.rethrowIfFatal`, which lets the non-recoverable throwables +through while leaving everything else for the caller to log or ignore: + +```java +try { + doSomethingRisky(); +} catch (Throwable t) { + ExceptionUtils.rethrowIfFatal(t); + options.getLogger().log(SentryLevel.ERROR, "Failed to do something risky", t); +} +``` + +Apply that pattern only where a broad catch is genuinely unavoidable — an entry point that runs +arbitrary user code or third-party callbacks. Everywhere else, name the exception types. Say in the +PR description why the broad catch is necessary. + +Two related cases: +- **Probing an optional `compileOnly` dependency**: a missing or version-mismatched class surfaces + as a `LinkageError` subclass (`NoClassDefFoundError`, `NoSuchMethodError`, `UnsatisfiedLinkError`), + which `rethrowIfFatal` deliberately rethrows. Catch the specific subclass locally *before* + delegating — see `SentrySQLiteDriver.hasConnectionPool` and `LoadClass`. +- **`InterruptedException`**: never swallow it. Either let it propagate or restore the interrupt + with `Thread.currentThread().interrupt()`; `rethrowIfFatal` does the latter for you. + ### Testing Requirements - Write comprehensive unit tests for new features - Android modules require both unit tests and instrumented tests where applicable From 46e37f1439d44fe90ee719a420fb8f3c5b8983eb Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Wed, 12 Aug 2026 16:31:47 +0200 Subject: [PATCH 2/2] docs(agents): Drop the two related cases from Exception Handling The LinkageError and InterruptedException notes were detail the core rule does not need. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 29d5b5cbfc..fd1fced0df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -181,14 +181,6 @@ Apply that pattern only where a broad catch is genuinely unavoidable — an entr arbitrary user code or third-party callbacks. Everywhere else, name the exception types. Say in the PR description why the broad catch is necessary. -Two related cases: -- **Probing an optional `compileOnly` dependency**: a missing or version-mismatched class surfaces - as a `LinkageError` subclass (`NoClassDefFoundError`, `NoSuchMethodError`, `UnsatisfiedLinkError`), - which `rethrowIfFatal` deliberately rethrows. Catch the specific subclass locally *before* - delegating — see `SentrySQLiteDriver.hasConnectionPool` and `LoadClass`. -- **`InterruptedException`**: never swallow it. Either let it propagate or restore the interrupt - with `Thread.currentThread().interrupt()`; `rethrowIfFatal` does the latter for you. - ### Testing Requirements - Write comprehensive unit tests for new features - Android modules require both unit tests and instrumented tests where applicable