testing-compose-in-release-mode — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited testing-compose-in-release-mode (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Compose ships unbundled — its UI runtime is loaded from app code, runs interpreted in debug, and the Live Literals plugin turns every constant into a getter. Debug recomposition counts, debug startup numbers, and debug compiler reports are not what users experience. This skill teaches Claude how to set up a release-with-symbols build for honest measurement before any perf claim is made.
CompilationMode.None.../../build/configuring-r8-for-compose/SKILL.md.../generating-baseline-profiles/SKILL.md.../../stability/diagnosing-compose-stability/SKILL.md.signingConfig signingConfigs.debug on the release type), but unsigned release builds will not install.org.jetbrains.kotlin.plugin.compose Gradle plugin.Surface these to the developer when they push back on "but my numbers feel real":
sourceInformation() calls, devirtualizes ComposerImpl, and constant-folds composable args.0.dp, "Hello", Color.Red — in a getter. The recomposer treats these as dynamic, so reports flag composables as taking unstable params even when source code only uses constants.sourceInformation strings remain, and the cost-per-frame budget is bigger than what release ships.Cited measurement: roughly 75 percent startup gain and 60 percent frame-render gain when switching debug to release with R8. Source: Ben Trengrove, "Why should you always test Compose performance in release" (Android Developers Medium).
../../build/configuring-r8-for-compose/SKILL.md for the full keep-rule story. Minimum:android {
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
// Use debug signing if there is no release keystore yet — the build still installs.
signingConfig = signingConfigs.getByName("debug")
}
}
}featureFlags enum exposes only IntrinsicRemember, OptimizeNonSkippingGroups, PausableComposition, and StrongSkipping. On the legacy Compose Compiler 1.5.x extension the property was liveLiterals, marked deprecated. For measurement, build the release variant — Live Literals is off by default in release. If a separate "benchmark" build type is used (recommended — release minus signing constraints), it inherits the release defaults.../../stability/diagnosing-compose-stability/SKILL.md for how to interpret the reports../gradlew :app:assembleRelease -PcomposeCompilerReports=true
ls app/build/compose_compiler/
# app_release-classes.txt, app_release-composables.txt, app_release-composables.csv, app_release-module.json../generating-baseline-profiles/SKILL.md.@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule val rule = MacrobenchmarkRule()
@Test fun startupRelease() = rule.measureRepeated(
packageName = "com.example",
metrics = listOf(StartupTimingMetric()),
iterations = 10,
startupMode = StartupMode.COLD,
compilationMode = CompilationMode.Partial(BaselineProfileMode.Require),
) {
pressHome()
startActivityAndWait()
}
}@TraceRecomposition from compose-stability-analyzer against a release-with-symbols build. Cross-link ../tracing-recompositions-at-runtime/SKILL.md.simpleperf and the Android Studio Profiler can resolve native (NDK) frames in the release variant:android {
buildTypes {
release {
isMinifyEnabled = true
// NDK / native symbol level only — controls .so debug symbol packaging.
ndk { debugSymbolLevel = "FULL" }
}
}
}ndk { debugSymbolLevel = "FULL" } controls NDK / native symbol packaging only; it does not affect Kotlin / Java frame readability. Kotlin frame readability comes from mapping.txt, which R8 always produces when isMinifyEnabled = true. Pair native profiling with R8 retrace (see ../../build/configuring-r8-for-compose/SKILL.md) to map obfuscated Kotlin stacks back to source lines.
# WRONG
./gradlew :app:assembleDebug
cat app/build/compose_compiler/debug/app-composables.txt
# WRONG because: debug enables Live Literals which turns every constant into a getter, so reports show false-positive unstable params and inflate non-skippable counts.# RIGHT
./gradlew :app:assembleRelease -PcomposeCompilerReports=true
cat app/build/compose_compiler/app_release-composables.txt// WRONG
rule.measureRepeated(
packageName = "com.example",
metrics = listOf(StartupTimingMetric()),
iterations = 5,
startupMode = StartupMode.COLD,
compilationMode = CompilationMode.None,
)
// WRONG because: CompilationMode.None disables AOT compilation and the target app may also be the debug variant — interpreted Compose stack plus JIT warmup makes startup numbers 3-4x worse than what users see, and any regression diff is dominated by JIT noise.// RIGHT
rule.measureRepeated(
packageName = "com.example",
metrics = listOf(StartupTimingMetric()),
iterations = 10,
startupMode = StartupMode.COLD,
compilationMode = CompilationMode.Partial(BaselineProfileMode.Require),
)
// And the targetPackage points to the release variant of the app under test.// WRONG
// "Layout Inspector says ProductCard recomposes 50 times per scroll, so the bug is real
// and we need to mark every parameter @Stable."
// WRONG because: Layout Inspector counts are sampled and approximate; Live Literals can inflate them; the count may double or halve in release. Confirm with @TraceRecomposition or Macrobenchmark FrameTimingMetric in a release build before chasing a fix.// RIGHT
// Step 1: Reproduce in release build with @TraceRecomposition on the suspect composable.
// Step 2: If the release-build trace still shows the count, then diagnose with
// ../../stability/diagnosing-compose-stability/SKILL.md against the release reports.
// Step 3: Fix, then re-measure with FrameTimingMetric in Macrobenchmark.
@TraceRecomposition(traceStates = true)
@Composable
fun ProductCard(product: Product) { /* ... */ }// WRONG
// "I ran my benchmark against the debug variant — Live Literals was on, so my recomposition
// counts and frame timings were inflated, and I cannot trust the report."
// WRONG because: Live Literals is on in debug to support Android Studio's live edit; it wraps
// constants in getters that the recomposer treats as dynamic. Stability reports and recomposition
// counts from a debug build are not measurement evidence.// RIGHT — measure release; Live Literals is off there by default in the new (Kotlin 2.0+)
// Compose Compiler Gradle DSL. The featureFlags enum exposes IntrinsicRemember,
// OptimizeNonSkippingGroups, PausableComposition, and StrongSkipping — there is no LiveLiterals
// flag to set. Just build the release variant and read the release reports:
composeCompiler {
reportsDestination = layout.buildDirectory.dir("compose_compiler")
metricsDestination = layout.buildDirectory.dir("compose_compiler")
}// WRONG
// "Startup is 1420 ms after my change."
// WRONG because: no variant, no device, no compilation mode, no iteration count — unreviewable. The same code measures 460 ms in release with R8 + a baseline profile on a Pixel 6.// RIGHT
// "Startup TimeToInitialDisplay p50 = 460 ms (release, R8 on, baseline profile required,
// Pixel 6 stock, cold start, 10 iterations, Macrobenchmark)."CompilationMode.None, or from emulator-only runs are diagnostic at best.build/compose_compiler/<module>_release-*). Debug reports are corrupted by Live Literals.@TraceRecomposition counts (deterministic, runtime-instrumented). Use the latter for any conclusion.ndk { debugSymbolLevel = "FULL" } on the release variant so simpleperf, the Android Studio Profiler, and crash retrace work without rebuilding.release (or a release-derived build type), not debug.isMinifyEnabled = true and proguard-android-optimize.txt are present on that variant.release (Live Literals is off by default there in the Kotlin 2.0+ Compose Compiler DSL — there is no LiveLiterals feature flag to toggle)._release suffix in their names (app_release-composables.txt, etc.).CompilationMode.Partial(BaselineProfileMode.Require) and the target app installed for the run is the release variant.@TraceRecomposition (or Macrobenchmark FrameTimingMetric) on a release build.@TraceRecomposition): https://github.com/skydoves/compose-stability-analyzer~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.