← Blog
CI/CD · 8 min read

How to build an AI agent harness around CI/CD with fastlane

AI coding agents can write a feature in minutes. The hard part is everything after that: tests, signing, builds and the moment code reaches real users. Here is how I wrap a mobile CI/CD pipeline around agents with fastlane, so they move fast and still can't ship anything on their own.

What “harness” means here

An AI agent harness is everything around the model that decides what it may touch, how its work is checked and who lets it out. For a mobile app the harness has a simple rule: the agent proposes, the pipeline verifies, a human releases. fastlane is a good backbone for this, because it turns every build, test and upload step into a named command that behaves the same on a laptop, on an agent box and in CI.

Layer 1 — one entry point for humans and agents

Agents get confused by ten ways of doing the same thing. Give them one. Every check lives in a fastlane lane, and the agent's instructions (an AGENTS.md or similar file in the repo) say exactly which lanes it may run.

default_platform(:ios)

platform :ios do
  before_all { setup_ci if ENV["CI"] }

  # Safe for agents: no secrets needed
  lane :check do
    run_tests(scheme: "App", devices: ["iPhone 16"])
  end

  # CI only: needs signing + App Store Connect access
  lane :beta do
    api_key = app_store_connect_api_key(
      key_id: ENV["ASC_KEY_ID"],
      issuer_id: ENV["ASC_ISSUER_ID"],
      key_content: ENV["ASC_KEY_P8"],
      is_key_content_base64: true
    )
    match(type: "appstore", readonly: true, api_key: api_key)
    increment_build_number(
      build_number: latest_testflight_build_number(api_key: api_key) + 1
    )
    build_app(scheme: "App", export_method: "app-store")
    upload_to_testflight(api_key: api_key, skip_waiting_for_build_processing: true)
  end
end

The check lane needs nothing secret, so an agent can run it as often as it likes. The beta lane can only succeed where release secrets exist — and they only exist in CI.

Layer 2 — keep signing out of the agent's reach

Code signing is the real “ship” button, so it is the first thing to fence off:

  • Certificates and profiles live in an encrypted match repository. CI reads them with readonly: true; nothing in the pipeline can create or revoke them.
  • App Store Connect access uses an API key stored as a CI secret, never a personal Apple ID and never a file in the repo.
  • The agent's environment has none of these. No MATCH_PASSWORD, no .p8 key. If an agent tries to run beta, it fails loudly — exactly what you want.

Layer 3 — CI that verifies, and a human who releases

Pull requests (including the ones agents open) run the safe lane. Merges to main run the release lane inside a protected environment that waits for a human reviewer before any secret is exposed.

name: ios
on:
  pull_request:
  push:
    branches: [main]

jobs:
  check:
    runs-on: macos-15
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
      - run: bundle exec fastlane ios check

  beta:
    if: github.ref == 'refs/heads/main'
    needs: check
    runs-on: macos-15
    environment: release   # required reviewers = the human gate
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
      - run: bundle exec fastlane ios beta
        env:
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
          ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
          ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
          ASC_KEY_P8: ${{ secrets.ASC_KEY_P8 }}

Store the release secrets as environment secrets on release, not repository-wide, so a job outside that environment cannot read them.

Layer 4 — protect the harness itself

An agent that can edit the pipeline can edit its own rules. Close that loop:

  • Branch protection on main: pull requests only, required status checks, at least one human approval.
  • A CODEOWNERS entry for fastlane/ and .github/workflows/, so any change to the harness needs an explicit human review.
  • The agent's git token can push branches and open pull requests — never push to main or change repository settings.

Android uses the same pattern

The Android side mirrors iOS: a check lane with gradle(task: "test"), and a CI-only lane that builds a release bundle and calls upload_to_play_store(track: "internal") with a Play service-account key stored as an environment secret. Start on the internal track; promotion to production stays a human decision.

Checklist

  • Every build, test and upload step is a fastlane lane.
  • Agents may run only lanes that need no secrets.
  • Signing via match in read-only mode; App Store Connect via an API key.
  • Release jobs run in a protected environment with a required reviewer.
  • CODEOWNERS guards fastlane/ and the CI workflows.

With this in place, agents can iterate as fast as they like inside the fence, and every build that reaches TestFlight or Play has passed the same tests and the same human gate. Building Android on a remote Linux machine? The next step is keeping that VPS on a private VPN.

Few spots left Free AI audit