

Also from Kynth Studios


Also from Kynth Studios


Also from Kynth Studios
1# LeakCanary — agent guide23A memory leak detection library for Android, plus Shark, the heap analyzer underneath it. Published4to Maven Central and consumed by a very large number of apps, so **the public API and the bytecode5level are contracts**, not implementation details.67Not equally, though. The `leakcanary*` modules are what apps depend on directly, and their public API8is the contract that matters most: breaking backward compatibility there is a last resort. `shark*` is9used far less, so a breaking change is on the table there when it buys a meaningful improvement. In10both cases the ABI dump is what makes the break deliberate instead of a surprise, so propose it rather11than assuming it's fine.1213This file records what an agent would get wrong from reading the source alone. Anything derivable by14reading the code belongs in the code, not here — please keep it that way when editing.1516## Layout1718| Directory | What's in it |19| --- | --- |20| `shark/` | Heap dump parsing and analysis. Plain JVM, no Android dependency, except `shark-android` which adds Android specific reference readers and matchers. |21| `object-watcher/` | Watches objects for retention. The lowest layer, usable on its own. |22| `leakcanary/` | The Android library, plus `leakcanary-app*`, a standalone UI app that is not part of the library. |23| `plumber/` | Fixes for known Android framework leaks, installed automatically. |24| `samples/` | Sample app. |25| `docs/` | The [documentation site](https://square.github.io/leakcanary/) content. |2627The group directories (`shark/`, `leakcanary/`, …) hold no code of their own — only modules do.2829## Build and test3031`docs/dev-env.md` is the contributor setup guide — code style, local deployment, examples of the32synthetic heap dump DSL. Read it for anything this section doesn't cover.3334Built with **Java 17**, targeting **Java 8 bytecode** repo wide. Both matter: consumers still on35Java 8 have to be able to use the artifacts.3637```bash38./gradlew build # what CI runs39./gradlew :shark:shark:test # one module's unit tests40./gradlew detekt # static analysis, also run by the pre-push hook41./gradlew updateKotlinAbi # after any public API change, see below42./gradlew siteDokka # regenerate docs/api43```4445`docs/api/` — the `siteDokka` output — is git ignored, not committed. The release process46regenerates it just before publishing the site (see `docs/releasing.md`), so a public API change47means updating the ABI dump and nothing else. If you do run `siteDokka`, don't edit what it writes;48fix the KDoc in the source.4950Instrumentation tests need a device or emulator and only cover `leakcanary-android`,51`leakcanary-android-core`, `leakcanary-android-instrumentation` and `leakcanary-android-test`. CI52runs them on one emulator per major Android release, from the minSdk to the newest API level with a53system image, so a change that only works on some API levels will fail there rather than locally.5455## Things that will bite you5657**Public API changes fail the build until the ABI dump is updated.** `checkKotlinAbi` compares the58public ABI against the committed `api/*.api` files and runs as part of `check`, so `./gradlew build`59catches it. When it fails, run `./gradlew updateKotlinAbi` and commit the changed `api/*.api` files —60but read the diff first, because an unintended ABI change is exactly what this is meant to catch.61Modules with no public API are exempt; they're listed in `modulesWithoutPublicApi` in the root62`build.gradle.kts`.6364**There are two ABI validation mechanisms, deliberately sharing task names** so that one command65covers the whole repo: the Kotlin Gradle plugin's `abiValidation()` for JVM modules, and an66equivalent pair of tasks hand-rolled in the root `build.gradle.kts` for Android library modules,67which KGP doesn't support yet ([KT-83410](https://youtrack.jetbrains.com/issue/KT-83410)).6869**So use `checkKotlinAbi`/`updateKotlinAbi`, never `checkLegacyAbi`/`updateLegacyAbi`.** The `Legacy`70pair is what Kotlin's own ABI validation documentation calls these tasks, but here they come straight71from KGP and therefore exist *only on the JVM modules* — they silently skip every Android library72module, which is most of the published ones. A green `updateLegacyAbi` means less than half the repo73was covered.7475**Some dependency versions are deliberately old.** The `compileOnly` AndroidX versions in76`gradle/libs.versions.toml` are pinned to the *lowest* version LeakCanary supports, so that apps77resolve to their own newer version without needing a resolution strategy. The inline comments say78which ones and why. Don't bump them to fix a warning.7980**`HprofRetainedHeapPerfTest` and `HprofIOPerfTest` freeze exact numbers** — bytes read, and memory81retained at each analysis step, within a margin. A change to how the analysis allocates or reads will82fail them. That's the point: they exist to make memory and I/O regressions visible. Investigate83before adjusting the expected values, and say in the PR why the new number is correct.8485**detekt runs on pre-push and in CI**, config at `config/detekt-config.yml`. The hook installs itself86via the `assemble` and `clean` tasks, so a fresh clone gets it after the first build. Run `detekt`87before pushing rather than discovering it at push time.8889**`gh pr merge --auto` does not wait for CI here — it merges on the spot.** Auto-merge is enabled on90the repo, but `main` is deliberately left unprotected, so there are no required status checks for91auto-merge to gate on. GitHub sees a mergeable pull request with nothing to wait for and merges92immediately, exiting zero and printing nothing, which reads exactly like it armed. Nothing in the93repo will stop a merge while CI is red — `main` is open on purpose — so waiting for green is your94job, not the platform's. Wait explicitly and let the exit code decide:9596```bash97gh pr checks <number> --watch --fail-fast && gh pr merge <number> --merge98```99100`gh pr checks` exits zero only once every check has passed, so the `&&` is what makes this safe;101`--fail-fast` returns as soon as one fails instead of sitting through the rest. A run takes 9 to 13102minutes, nearly all of it the emulator matrix, so start that command detached — a foreground call103that gives up at ten minutes will usually be killed just before the last emulator reports.104105## Changelog106107Entries go in `docs/changelog.md` under `## Unreleased`, each starting with one of the markers from108the legend at the top of that file. Pick the marker from what the change *is*, not from how big it109feels, and grep for a comparable existing entry rather than guessing.110111**💥 means a crash fix here, not a breaking change** — the opposite of the112[gitmoji](https://gitmoji.dev/) convention, and the mistake that convention trips people into.113Reaching for 💥 because a change feels impactful tells readers a crash was fixed when nothing114crashed. Breaking changes are ⚠️; when one needs more than a bullet, write it as a115`### Breaking change: <summary>` heading with prose.116117**The changelog is for changes that matter to the people consuming LeakCanary**, not a record of118every diff. Refactors, internal cleanups and test-only changes usually don't need an entry.119120## Conventions121122- When a function's parameters don't fit on one line, put **each on its own line** — the existing123 code is consistent about this and detekt won't tell you.124- Commit subjects are imperative and describe the change, e.g. "Keep modules without a public API off125 the documentation site". Explain *why* in the body when it isn't obvious.126- Don't leave test-only or unused code in the committed tree. If scaffolding was needed to get127 somewhere, remove it before the PR lands.128- Test heap dumps are built with the `dump { }` DSL from `shark-hprof-test` (see `docs/dev-env.md`)129 rather than committed as binary fixtures. Never hand-assemble hprof bytes. For a large realistic130 dump, drive a real JVM via `HotSpotDiagnosticMXBean.dumpHeap`.131- Tests use JUnit 4 and AssertJ. Instrumentation tests depend on `libs.assertjCore.android` rather132 than `libs.assertjCore`, because AssertJ 3.16 and up can't load on API 24 — the catalog comment133 says why. So an assertion that a unit test can use may not compile in an instrumentation test.134135## Scoped guides136137Subdirectories may carry their own `AGENTS.md`, and the closest one to the file being edited wins,138like `.gitignore`. Prefer putting guidance in the narrowest place it applies over growing this file —139a module's quirks belong next to the module.140141Claude Code reads `CLAUDE.md` rather than `AGENTS.md`, so each `AGENTS.md` is paired with a142`CLAUDE.md` containing `@AGENTS.md`. Add both when adding a scoped guide.143
One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| square/leakcanaryshark/shark-explorer/AGENTS.md · 30k | AGENTS.md | buildteststylearch+5 | 76/100 | today |
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| vllm-project/vllmAGENTS.md · 89k | AGENTS.md | setuptestlint-formatstyle+5 | 100/100 | 14 days ago | |
| duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70 | AGENTS.md | buildteststylearch+3 | 100/100 | 14 days ago | |
| TryGhost/Ghoste2e/AGENTS.md · 55k | AGENTS.md | setupteststylearch+2 | 100/100 | today | |
| deepseek-ai/deepseek-harnessnative/landlock-run/AGENTS.md · 104k | AGENTS.md | setupteststylearch+3 | 100/100 | today | |
| aaif-goose/gooseAGENTS.md · 53k | AGENTS.md | setupbuildtestlint-format+7 | 100/100 | 8 days ago | |
| n8n-io/n8npackages/@n8n/agents/AGENTS.md · 201k | AGENTS.md | buildteststylearch+3 | 100/100 | 14 days ago | |
| code-yeongyu/oh-my-openagentpackages/web/AGENTS.md · 68k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 13 days ago | |
| mui/material-uiAGENTS.md · 99k | AGENTS.md | setupbuildtestlint-format+9 | 100/100 | 14 days ago |
A badge carrying the measured quality of the strongest agent config file in this repository, out of 100. It reads from this index every time somebody loads your page, so it changes when the measurement changes and there is nothing to keep up to date. Free, no account, and the value is not something you or we can set by hand.
[](https://rulestack.kynth.studio/configs/square-leakcanary-agents)Would rather not hotlink us? Every badge is also served in shields.io’s endpoint schema, so shields renders the image and your readers never talk to our domain:
Published by Toolproof, the masthead over this index and eight others. The method behind the number is at toolproof.kynth.studio/methodology, and the whole thing is readable as JSON with no key at /api.