Skip to content
Back to skills

Ship Fastlane

ASecurity

Vanilla fastlane TestFlight pipeline. ONLY invoke this skill when the user explicitly types /ship-fastlane. Never trigger automatically on "ship", "TestFlight", or general release requests — those go to /expo-local-ship instead. Use when shipping from a machine without the maintainer signing env vars, when wiring up CI, or as a vanilla fallback if /expo-local-ship is misbehaving. Before running `bundle exec fastlane beta`, confirm with the user and wait for explicit approval.

  • 4 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 3, 2026
developmentgorubyshellbashgitapi

Works with

  • api

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned September 3, 2026

npx -y skills add RonenMars/threadbase-mobile --skill ship-fastlane --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ship Fastlane?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Ship Fastlane
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ronenmars-ship-fastlane/badge)](https://www.skillsdirectory.com/skills/ronenmars-ship-fastlane)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ship-fastlane
description: >
  Vanilla fastlane TestFlight pipeline. ONLY invoke this skill when the user
  explicitly types /ship-fastlane. Never trigger automatically on "ship",
  "TestFlight", or general release requests — those go to /expo-local-ship
  instead. Use when shipping from a machine without the maintainer signing
  env vars, when wiring up CI, or as a vanilla fallback if /expo-local-ship is
  misbehaving. Before running `bundle exec fastlane beta`, confirm with the
  user and wait for explicit approval.
---

# Ship via Fastlane

Standard fastlane setup for archiving the iOS app and uploading to TestFlight.
TestFlight only — no App Store submission, no polling until VALID, no
custom signing bootstrap. The lane is intentionally minimal; if you need the
full pipeline, use `/expo-local-ship`.

## When this skill applies

Only when the user explicitly types `/ship-fastlane`.

Do **not** invoke on:

- "ship", "ship it", "ship to TestFlight" — those go to `/expo-local-ship`.
- "build", "archive", "upload" — same.
- "fastlane" appearing in conversation context — wait for the explicit slash command.

If a user asks "should I ship?" or "how do I ship?", point them at
[`docs/deployment.md`](../../../docs/deployment.md) and let them pick a path.

## Project quick reference

| Key | Value |
|-----|-------|
| Workspace | `ios/$APP_SCHEME.xcworkspace` |
| Scheme | from `$APP_SCHEME` env var |
| Bundle ID | from `$APP_BUNDLE_ID` env var |
| Marketing version | read from `app.json` → `expo.version` |
| Build number | derived from App Store Connect on every run |
| Signing | automatic, via ASC API key |
| Output | TestFlight (no App Store submission) |

## Prerequisites

1. **Ruby + bundler** — System Ruby on macOS is fine. `gem list bundler` should show something.
2. **App Store Connect API key** — `.p8` file with the **App Manager** role, downloaded once from <https://appstoreconnect.apple.com/access/integrations/api>. Store outside the repo (`~/.appstoreconnect/keys/` recommended).
3. **`fastlane/.env`** — local, gitignored. Copy from `fastlane/.env.example` and fill in:
   - `APP_BUNDLE_ID`
   - `APP_SCHEME`
   - `APPLE_TEAM_ID`
   - `ASC_KEY_ID`, `ASC_ISSUER_ID`, `ASC_KEY_PATH`
4. **`bundle install`** — once per fresh clone. Installs fastlane into `vendor/bundle/` (gitignored).
5. **`ios/` directory present** — either persistent or generated by a prior `npx expo prebuild --platform ios`. The lane does **not** run prebuild.

## Daily command

```bash
bundle exec fastlane beta
```

That's the whole thing. Don't pass extra flags unless you know why.

## What the lane does (in order)

1. **Pre-ship checks** — shells out to `./scripts/git-sync-check.sh`. Fails fast if:
   - `app.json` has uncommitted edits (we don't ship from a dirty tree).
   - Local `main` is behind `origin/main` (another machine may have pushed a build-number bump).
   - On a non-`main` branch, prints a warning but proceeds.

   See [`.Codex/skills/_shared/pre-ship-checks.md`](../_shared/pre-ship-checks.md) for the rationale behind these checks. They mirror the gates `./scripts/ship-ios.sh` runs.

2. **Authenticate to ASC** — `app_store_connect_api_key` with the three `ASC_*` env vars. No Apple ID prompt, no 2FA.

3. **Determine next build number** — `latest_testflight_build_number` for the current marketing version, then `+ 1`. ASC is the source of truth.

4. **Write build number** into `app.json` (for `Info.plist` regeneration after a future `expo prebuild`) and directly into `ios/$APP_SCHEME/Info.plist` (used by the in-flight archive).

5. **Archive + sign + export** — `build_app` with the inline `export_options` hash (`teamID`, `signingStyle: automatic`, symbols), passing ASC API key flags via `xcargs` so xcodebuild can refresh provisioning profiles non-interactively.

6. **Upload to TestFlight** — `upload_to_testflight` with `skip_waiting_for_build_processing: true`. Returns as soon as the upload completes; ASC processing finishes asynchronously.

7. **Restore `app.json`** — `git restore app.json`. The build number that was written in step 4 was derived data for this archive only; committing it would create churn. Working tree returns to clean.

## What the lane does NOT do

| Missing | What to use instead |
|---|---|
| Maintainer signing env bootstrap | `./scripts/ship-ios.sh` (Path A in `docs/deployment.md`) |
| `expo prebuild` if `ios/` is missing | Run `npx expo prebuild --platform ios` first, or use `ship-ios.sh` |
| Poll ASC until the build is VALID | Check TestFlight manually, or use `ship-ios.sh` |
| Submit for App Store review | `ship-ios.sh --target production` |
| Auto-install npm dependencies | Run `npm install` (or your package manager) first |

## After a successful run

Nothing to commit. `app.json` was restored by step 7. `git status` will be clean.

The uploaded build appears on TestFlight after ASC processing finishes — usually 5–30 minutes. You can refresh the TestFlight tab in App Store Connect, or query the ASC API.

## Troubleshooting

### `Error packaging up the application`

You're missing one or more of the export config pieces. Verify:

- `APPLE_TEAM_ID` is set in `fastlane/.env`.
- `ASC_KEY_PATH` points at a `.p8` file that actually exists (`ls $ASC_KEY_PATH`).
- The Xcode project's signing is set to **Automatic** (the lane doesn't override this — whatever the project says, applies).

### `Error setting value 'app-store-connect' for option 'export_method'`

This means someone changed `export_method:` in the Fastfile to `app-store-connect`. Fastlane only accepts the legacy `app-store`. Revert to `export_method: "app-store"`.

### Lane refuses to run because `main` is behind `origin/main`

Another machine (or you, on another laptop) pushed commits since you last pulled. Run:

```bash
git checkout main
git pull --ff-only
```

Then re-run the lane. This is intentional — see [`_shared/pre-ship-checks.md`](../_shared/pre-ship-checks.md).

### Lane refuses to run because `app.json` is dirty

Commit or stash the changes first. If your dirty `app.json` is just the lane's own previous write that wasn't restored (e.g. the lane crashed before the cleanup step):

```bash
git restore app.json
```

### Missing env var

`ENV.fetch("APP_BUNDLE_ID")` (or similar) raised `KeyError`. Check `fastlane/.env` against `fastlane/.env.example` — make sure every required field is filled in.

## Anti-patterns

- **Don't pass `--no-build` or `--no-upload` flags** to skip parts of the lane. Either the whole thing runs or you're not actually shipping.
- **Don't commit `app.json` after a run.** The lane already restored it; if you see "modified: app.json" in `git status` after the lane finished, something went wrong with step 7 — fix that rather than committing the bump.
- **Don't add new lanes for "staging," "preview," etc.** without a clear scope. The simplicity is the point. Use `fastlane/.env.production` / `.env.staging` for environment switching, not new lanes.
- **Don't auto-fire on "ship"** — that's `/expo-local-ship`'s job. Read the trigger boundary at the top of this file.

## See also

- [`docs/deployment.md`](../../../docs/deployment.md) — full comparison of ship paths (this skill is Path B).
- [`fastlane/Fastfile`](../../../fastlane/Fastfile) — the lane source.
- [`fastlane/.env.example`](../../../fastlane/.env.example) — env var template.
- [`_shared/pre-ship-checks.md`](../_shared/pre-ship-checks.md) — canonical pre-ship policy.
- `/expo-local-ship` — full pipeline (signing bootstrap, polling, production submission).
- `/ship-expo-cloud` — EAS cloud builds (opt-in, costs money).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.