My Profile Photo

Chang Min Park


Senior Software Engineer at Yahoo!



GitHub Actions for Android: PR Gates That Protect Merges

Green checks that mean nothing

On GitHub, Android teams often land in one of two traps: a required check that only runs assembleDebug, or a forty-minute matrix nobody trusts. PRs still merge with broken module graphs, Manifest surprises, and “LGTM” from humans who never opened the Gradle diff. Org platforms like Jenkins or Screwdriver matter at release scale — see Jenkins to Screwdriver — but day-to-day merge safety is usually a developer-owned Actions workflow.

This post is about designing that merge contract: what must be true before main moves, without turning every PR into a release rehearsal.

The merge contract (mobile-shaped)

A useful PR gate answers three questions fast:

  1. Will this compile the cone it touches? Prefer affected-module assemble over whole-monorepo when possible.
  2. Did we introduce a graph or Manifest footgun? Cycles, forbidden edges (featurefeature), unexpected api exports, dangerous permissions.
  3. Is the diff reviewable? Formatters and linters as required checks — ktlint/detekt/Spotless — so humans review behavior, not spaces. Details live in the formatter vs linter post; here the point is required status.

Optional but high leverage: mapping-file / R8 smoke on release-track PRs, unit tests for touched modules, and cache (Gradle + dependencies) so the latency budget stays under “I’ll wait.”

Bots as signal, not authority

Claude / Copilot (or similar) on raised PRs can catch API/impl leaks, Manifest merges, missing tests, and “this PR edits generated/.” They cannot own product risk or rollout judgment. Treat bot review as a noisy sensor:

Bot checks Human keeps
Graph / style / obvious API misuse Product risk, rollout, privacy
Missing tests for pure functions Experiment design
Dangerous permission diffs “Should we ship Friday?”

If the bot is wrong in public, someone must own the thread — same lesson as event-driven PR agents elsewhere on this site.

Branch protection that matches reality

Required status checks only help if they are the same jobs developers run locally (or a documented subset). No self-approve. Path filters for docs-only PRs so README typo fixes do not wait on emulator farms. Secrets for signing stay on protected branches — PR forks get unsigned assemble.

# Sketch — not a full production workflow
jobs:
  gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup JDK
        uses: actions/setup-java@v4
        with: { distribution: temurin, java-version: "17" }
      - name: Style
        run: ./gradlew ktlintCheck detekt
      - name: Unit (affected)
        run: ./gradlew testDebugUnitTest
      - name: Assemble (affected)
        run: ./gradlew assembleDebug

Wire flake policy explicitly: quarantine flaky instrumentation; do not train the team to re-run until green.

Latency budget for PR feedback

If the gate routinely exceeds ~10–15 minutes for ordinary app PRs, developers will work around it — force-pushing, marking checks optional, or merging with “I’ll fix CI later.” Prefer a fast required path (style + unit + assembleDebug cone) and a slower non-blocking or nightly path (full instrumentation, size analysis). Document which jobs are merge-blocking. Flakes belong in quarantine with an owner, not in tribal knowledge about which “re-run” button to mash.

Wrap-up

GitHub Actions for Android should encode a merge contract, not a green vanity check. Combine style gates, graph/Manifest sanity, and compile cones with branch protection — and keep bot review as signal. Leave org-scale promotion and store signing theater to platforms like Screwdriver; keep PR feedback inside a latency budget humans will actually wait for.

References