Skip to main content
How-ToSeptember 5, 2026·15 min read

Automated Code Review for Mobile Repos With Claude Code

A reviewer with no memory of writing the code catches what the author cannot. This is how to run one on mobile pull requests, the runner cost trap that surprises people, and an honest account of what it does not catch.

ByAmol Pomane·Founder, Vmobify
A pull request reviewed by a second agent with no memory of writing the code

What does a clean-context reviewer catch?

A reviewer with no memory of writing the code sees it as a reader would, which is exactly the perspective the author has lost.

The same diff read by its author and by a reviewer with no prior context
This is why human code review works, and it transfers directly.

Before the YAML, be clear about what you are buying, because it is easy to set this up and then be vaguely disappointed.

An automated reviewer is good at things that are mechanical, checkable and boring. Did this diff add a permission? Did the lockfile change without the manifest changing? Is there a network call in a component body? Did minSdk move? Are there credentials in a config file that just got committed? These are exactly the checks a human skips at 6pm on a Friday, and exactly the ones that cost you a store rejection or a support thread later.

It is not good at judging whether your architecture is right, whether the feature is worth building, or whether the abstraction will hold up in six months. Do not build a workflow that pretends otherwise, because a reviewer that produces confident opinions about design decisions gets muted within a fortnight, and then it is not reviewing anything.

A concrete version of the difference. An agent adds an image picker to a profile screen. The diff is thirty lines: a dependency, a permission string, a handler, a state update. The authoring agent reviews it and reports that the feature works, which is true. A clean-context reviewer with mobile criteria reports four things instead — a new photo-library permission with a placeholder usage description, a dependency that appeared in the lockfile without a manifest change, a decode running on the main thread, and a strongly captured self in the completion handler. None of those stop the feature working. All four are the kind of thing that surfaces later as a rejection, a crash cluster or a one-star review about the app freezing.

Set the expectation at "a very consistent checklist that never gets tired" and it earns its place permanently. The limits of review as a quality mechanism are covered in why code review cannot judge visual quality.

How do you set it up?

Setup is short, and the decisions that matter are about scope rather than configuration.

The fastest path is the one Anthropic documents: run

claude /install-github-app

from inside your repository. This needs repository admin rights — it installs a GitHub App and writes the secrets and workflow scaffolding. If you are not an admin on the repo, that is the blocker to solve first; there is no way around it from the CLI.

What you end up with is anthropics/claude-code-action@v1 wired to trigger on pull requests and on a trigger phrase in comments.

The inputs the action actually accepts, verified against the action itself:

  • anthropic_api_key
  • claude_code_oauth_token
  • prompt
  • claude_args
  • track_progress
  • use_sticky_comment
  • classify_inline_comments
  • settings
  • additional_permissions
  • trigger_phrase (defaults to @claude)
  • label_trigger
  • assignee_trigger
  • branch_prefix
  • plugin_marketplaces
  • plugins
  • use_commit_signing / ssh_signing_key
  • use_bedrock / use_vertex
  • path_to_claude_code_executable

A few of those are worth calling out for this use case.

use_sticky_comment keeps the review in a single updating comment rather than adding a new one on every push. On a branch that goes through eight force-pushes, this is the difference between a readable PR and a wall.

classify_inline_comments puts findings on the lines they refer to instead of in one long block at the bottom. For mobile review, where a finding is usually "this specific line blocks the main thread", inline is substantially more useful.

track_progress gives you a live checklist as the review runs, which matters mostly because a mobile review prompt has a lot of sections and you want to see it did not stop after three of them.

label_trigger and assignee_trigger let you run the expensive review on demand rather than on every push — useful once you have a sense of your actual spend.

You can also get structured output by passing --json-schema inside claude_args, which is the hook to use if you want to feed findings into your own tooling rather than only into a PR comment.

mcp_config is deprecated

State this plainly because a lot of circulating examples still use it: mcp_config is deprecated. Do not copy an example that sets it.

The supported ways to attach MCP servers now are:

  • claude_args: --mcp-config '{...}', passing the configuration through to the CLI directly, or
  • the settings JSON input

This matters for mobile specifically, because MCP is how you give the reviewer access to crash data and device state — which is the section below. If you wire it the deprecated way you will be rewriting it soon.

The primary reference for this is anthropics/claude-code-action — worth reading in full rather than taking a summary of it, because the details here change more often than the shape of the advice does.

What is the macOS runner cost trap?

macOS runners bill at a large multiple of Linux, which turns an innocuous trigger into a monthly surprise.

Linux and macOS runner costs compared for the same review job
Builds need a Mac. Reading a diff does not.

This is the item that turns a good idea into a surprising invoice, and almost nobody mentions it.

GitHub-hosted macOS runners bill at a 10x minute multiplier. One minute on macos-latest costs the same as ten minutes on ubuntu-latest. That multiplier is a billing decision, not a performance one, and it applies to every minute the job is alive — including the minutes spent checking out, installing dependencies and waiting.

The instinct for an iOS repo is to run everything on macOS. Resist it for review, because the review does not need Xcode. Reading a diff, reasoning about [weak self], spotting a permission added to Info.plist, noticing that Podfile.lock changed — all of that is text analysis. It requires no simulator, no toolchain and no code signing.

So the split is:

  • Review runs on ubuntu-latest. It reads text.
  • Build and test run on macos-latest, because compiling and running an iOS app genuinely requires a Mac.

If you have a single macOS job doing lint, review, build and test together, splitting the review out is the single largest CI cost reduction available to you, and it costs you nothing in review quality.

The Android side has an equivalent worth knowing: ReactiveCircus/android-emulator-runner gives you a hardware-accelerated emulator on Actions, and Linux runners need KVM enabled for it. That is a build-and-test concern, not a review concern, and it belongs in the cheap-runner tier where it can stay.

The documentation worth reading before you act on this is Claude Code documentation — worth reading in full rather than taking a summary of it, because the details here change more often than the shape of the advice does.

How do path filters narrow the trigger?

Filtering by path is the single most effective cost control, because most pull requests do not need a mobile reviewer at all.

A review that runs on every documentation typo trains you to ignore it. Filter the trigger so it fires when mobile code changes and stays quiet otherwise.

The set worth starting from:

  • ios/**
  • android/**
  • lib/** (Flutter)
  • src/** (React Native)
  • **/*.gradle
  • **/Podfile.lock

The two lockfile-adjacent entries earn their place. A dependency change is the most under-reviewed high-risk diff in mobile — it is where new permissions arrive, where app size jumps, where minSdk quietly moves, and where an SDK you never chose starts collecting data you then have to declare in Data Safety. A Podfile.lock diff with no other change should still get a review; that is exactly the PR a human waves through.

This is documented directly in GitHub Actions billing — worth reading in full rather than taking a summary of it, because the details here change more often than the shape of the advice does.

What other cost controls are worth applying?

Beyond the runner choice, a few settings keep the bill proportionate to the value.

Picking the cheap runner is the big lever. Three smaller ones are worth setting at the same time, because together they decide whether this stays on in month three.

Skip drafts. Add ready_for_review to your trigger types and let draft pull requests run nothing. A branch that gets pushed eleven times while you think out loud should not be reviewed eleven times. You want the review at the moment you are asking someone else to look, which is exactly what marking a PR ready means.

Consider label_trigger for expensive repositories. Running on every synchronize is the right default for a small repo with a few PRs a week. On a busy repo, moving to a label — apply needs-review and the workflow fires — turns an ambient cost into a decision you make. assignee_trigger does the same thing keyed on assignment.

Keep the prompt scoped to the diff. The instruction in the prompt below to review only what the diff changes is not just about noise; it also bounds how much of your repository the reviewer reads. A prompt that invites a wander through the codebase costs more and returns less.

The combination that works for most solo and small-team mobile repos: ubuntu-latest, paths filters, ready_for_review plus synchronize, and a sticky comment. That is a review on every meaningful change, at a cost that does not make you think about switching it off.

What does the workflow file look like?

Read this before you copy anything below. The YAML that follows is a construction built from the documented action inputs. It is not an official Anthropic sample, and Anthropic has published no mobile-specific example. Every input in it is real; the arrangement is mine. Check it against the action's own README before you rely on it, and expect the input surface to move.

# CONSTRUCTED EXAMPLE — not an official Anthropic sample.
# Built from the documented inputs of anthropics/claude-code-action@v1.
# Verify against the action's README before use.

name: Mobile PR review

on:
  pull_request:
    types: [opened, synchronize, reopened]
    paths:
      - 'ios/**'
      - 'android/**'
      - 'lib/**'
      - 'src/**'
      - '**/*.gradle'
      - '**/Podfile.lock'

jobs:
  review:
    # ubuntu, deliberately: the diff needs no Xcode, and macOS runners
    # bill at a 10x minute multiplier.
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
      issues: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          use_sticky_comment: true
          classify_inline_comments: true
          track_progress: true
          prompt: |
            <the mobile review prompt from the next section>

Two notes on the shape of it.

fetch-depth: 0 is there so the reviewer can see history rather than only the tip commit. Some of the most useful checks — did this lockfile drift, has this permission been here all along or is it new — need more than one commit of context.

The permissions block is minimal on purpose: read the code, write to the PR. A reviewer does not need write access to your repository contents, and giving it any is an unnecessary risk surface.

What should the mobile review prompt say?

The prompt decides what the reviewer looks for, and a generic prompt produces generic review.

This is the part that makes it a mobile reviewer rather than a generic one. A generic prompt produces generic findings; specificity in, specificity out.

You are reviewing a pull request in a mobile application repository.
Review only what this diff changes. Do not comment on pre-existing code
unless the diff makes an existing problem materially worse.

Work through every section. If a section has no findings, say so explicitly
rather than skipping it.

1. MAIN-THREAD BLOCKING
   Flag any synchronous work introduced on the main or UI thread: file I/O,
   JSON parsing of non-trivial payloads, image decoding, database queries,
   cryptographic work, or network calls. On iOS check for work dispatched to
   the main queue that does not need to be there. On Android check for work in
   lifecycle callbacks and composable bodies. In React Native, flag heavy
   synchronous work on the JS thread and anything that will run inside a
   render pass.

2. MEMORY AND RETAIN CYCLES
   In Swift and Objective-C, flag every escaping closure that captures self
   strongly where [weak self] is warranted — especially inside completion
   handlers, Combine sinks, Task blocks, and notification observers. In Kotlin
   and Java, flag listeners, callbacks and coroutine scopes that outlive their
   host. In React Native, flag subscriptions, timers and event listeners added
   without a matching cleanup in the effect's teardown.

3. COMPOSE RECOMPOSITION
   For Jetpack Compose changes, flag unstable parameters passed to composables,
   lambdas allocated inside composable bodies, state reads that widen the
   recomposition scope unnecessarily, and expensive work not wrapped in
   remember or derivedStateOf.

4. DEPENDENCY AND LOCKFILE DRIFT
   If Podfile.lock, Package.resolved or a gradle lockfile changed, report every
   dependency added, removed or version-bumped. For each addition, say what the
   dependency does and flag whether it plausibly collects user data — because
   that triggers a Data Safety and privacy-manifest obligation. Flag any
   lockfile change with no corresponding manifest change, and any manifest
   change with no corresponding lockfile change.

5. PERMISSIONS
   List every permission or usage-description key added to Info.plist or
   AndroidManifest.xml in this diff. For each, identify the code path in this
   diff that uses it. A new permission with no corresponding usage is a store
   review risk and should be flagged as high severity. Flag any usage-description
   string that is a placeholder or generic.

6. APP SIZE
   Identify anything in this diff likely to move binary size measurably: new
   dependencies, bundled assets, uncompressed images, added fonts, embedded
   models or data files. Give a rough direction and magnitude rather than a
   precise number.

7. SECRETS AND CONFIG
   Flag any credential-shaped string added to the diff. Pay specific attention
   to google-services.json, GoogleService-Info.plist, .env files, xcconfig
   files and CI configuration. Say for each whether it is genuinely public
   configuration or a real secret that must not be committed.

8. PLATFORM VERSION FLOORS
   Flag any change to minSdkVersion, targetSdkVersion, compileSdkVersion, the
   iOS deployment target, or a Podfile platform line. State which existing
   users would be cut off by the change.

FORMAT
Group findings by severity: critical, high, medium, nit. Each finding gets a
file path, a line number, one sentence on why it matters on a phone
specifically, and a concrete fix. If the diff is clean, say so in one line —
do not manufacture findings to look useful.

The last instruction is not decoration. A reviewer that feels obliged to produce output will produce output, and a stream of invented nits is how these tools get switched off. The security items worth putting in the prompt are in the mobile security guide.

How do you give the reviewer live data?

Connecting the reviewer to live data changes it from a linter into something that can check claims.

Everything so far is static analysis of a diff. The step that makes the review noticeably better is letting it correlate the diff with what is actually happening in your app.

Two MCP servers are worth wiring in, both attached through claude_args: --mcp-config '{...}' or the settings JSON — not the deprecated mcp_config input.

Sentry. Available as a plugin (claude plugin install sentry-mcp@sentry-mcp) or over HTTP at https://mcp.sentry.dev/mcp/{org}/{project}. It gives the reviewer search_events and search_issues, event inspection, traces and Seer root-cause analysis. Sentry has iOS, Android, React Native and Flutter SDKs, so the crash-to-fix loop is clean across the stacks that matter here.

What that changes about a review: instead of "this closure captures self strongly", you can get "this closure captures self strongly, and there are 340 events in the last week on a crash in this same file". The second version gets fixed.

One caveat that will bite you: the natural-language search tools need your own LLM provider key via EMBEDDED_AGENT_PROVIDER. Without it, those specific tools do not appear at all, and you will be confused about why the reviewer is ignoring an obvious instruction to search issues.

Maestro. The MCP is bundled with the CLI — claude mcp add maestro -- maestro mcp, no separate package — and exposes list_devices, inspect_screen, take_screenshot, run, cheat_sheet and the cloud-run tools. This is device access rather than crash data: the reviewer can run a flow against the change and look at the result.

Be realistic about where this belongs. Driving a device needs a device, which means it does not belong in the cheap ubuntu-latest review job — it belongs in the macOS build-and-test job, or in a local pre-push pass. Keep the PR reviewer reading text and let the device work happen where a device already exists. There is more on that loop in driving simulators and devices from Claude Code.

If you use assertWithAI in those flows, remember it is experimental and defaults to optional: true, meaning a failed assertion does not fail your run. That default is a false-confidence generator if you forget it is there.

For the authoritative version, see GitHub Actions workflow syntax — worth reading in full rather than taking a summary of it, because the details here change more often than the shape of the advice does.

Should you run CodeRabbit alongside it?

Running two reviewers is defensible if they are looking for different things.

CodeRabbit is worth pairing rather than choosing between, because it does a different job: it runs actual linters and gives you deterministic findings, where the Claude reviewer gives you reasoning.

Mobile-relevant linters it runs: SwiftLint, detekt, ESLint and Oxlint. For an iOS, Android or React Native repository that is respectable coverage of the mechanical layer.

The CLI installs via curl -fsSL https://cli.coderabbit.ai/install.sh | sh or brew install coderabbit, then cr auth login. Two commands matter for agent use:

  • coderabbit review --agent emits structured JSON designed for an agent to consume. This is the real integration point: Claude Code runs the review, reads the JSON, and works through the findings with full repository context — rather than you copying comments out of a web UI.
  • cr review --light is a fast local pass, useful before you push rather than after.

The free tier is rate-limited daily, which is fine for a solo developer and constraining for a team.

Now the gap, and it is a real one: CodeRabbit lists no Dart or Flutter tooling. No Dart analyzer, no Flutter lints. If you are a Flutter team, the linting half of this pairing simply does not cover your primary language, and the correct response is to lean harder on the Dart side of your own toolchain — the official Dart MCP server ships inside the Dart SDK (claude mcp add --transport stdio dart -- dart mcp-server) and exposes analyzer diagnostics directly, though it is explicitly experimental and needs Dart 3.9+.

That is not a criticism of CodeRabbit so much as a fact to plan around. Flutter is consistently the least-served stack in agent tooling, which is part of the framework choice calculation whether or not anyone frames it that way.

What does this setup not catch?

An automated reviewer does not see the running app, which bounds what it can tell you.

What an automated reviewer catches compared with what only a running app reveals
It never sees the app run, which bounds what it can tell you.

Three limits worth stating so you calibrate correctly.

It does not replace a human on anything that requires taste. It will not tell you the feature is wrong, the flow is confusing, or the abstraction will not survive the next requirement.

It does not catch what is not in the diff. A permission added three months ago is invisible to a diff reviewer. That is what the one-off audit in the security post is for; run it periodically, not once.

It does not know your product. It cannot tell you that the screen you just changed is the one where 40% of your users drop off. Only your own analytics can, and correlating that with code changes is still a human act.

What it does do, reliably, is check thirty boring things on every single change without getting bored. Over a year of an agent-assisted codebase, that is worth considerably more than it sounds.

The workflow YAML and the clean review prompt both live at [../assets/mobile-pr-review.yml](../assets/mobile-pr-review.yml) so you can pull them without re-reading this post. The loop that does see the running app is giving your agent eyes, and the wider path is the mobile shipping pillar.

Frequently Asked Questions

Why does a second agent catch things the first missed?+

Because it has no memory of writing the code. The author reads what they intended; a clean-context reader sees what is actually there.

Do I need macOS runners for mobile review?+

Usually not for review itself. Reading a diff does not require a Mac; building an iOS app does. Keep the reviewer on Linux and the builds separate.

How much does this cost to run?+

It depends almost entirely on trigger scope. Reviewing every pull request on macOS runners is expensive; reviewing mobile paths on Linux is not.

What should the review prompt include?+

The mobile-specific things you actually care about — safe areas, entitlement checks, secret handling, accessibility — rather than a general request to review the code.

Is the workflow file official?+

No. It is a construction shown as a starting point, and is labelled as such. Treat it as an example to adapt rather than a sample to copy.

Can it replace human review?+

No. It catches a consistent class of defects reliably, which frees human review for the things that need judgement rather than replacing it.

Does it catch visual problems?+

No. It never sees the app run. Visual and interaction defects need a device loop, which is a different tool doing a different job.

Sources

  1. anthropics/claude-code-action
  2. Claude Code documentation
  3. GitHub Actions billing
  4. GitHub Actions workflow syntax
  5. ReactiveCircus/android-emulator-runner
  6. Sentry MCP
  7. Maestro
  8. CodeRabbit documentation

About the author

Amol Pomane Founder, Vmobify

Amol leads Vmobify, a mobile app growth agency that has driven 30M+ downloads and ranked 54K+ keywords across 300+ apps since 2013. He writes about ASO, paid user acquisition, retention, and the operational reality of scaling mobile apps in India and global markets.

Related Articles

How to Actually Ship a Mobile App With Claude Code
How-To

How to Actually Ship a Mobile App With Claude Code

Read →
Why Code Review Cannot Tell You Whether a Product Looks Good
How-To

Why Code Review Cannot Tell You Whether a Product Looks Good

Read →
Your Vibecoded App Is Leaking: Mobile Security Basics
How-To

Your Vibecoded App Is Leaking: Mobile Security Basics

Read →