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.
Scanned 9/3/2026
Install to Claude Code
npx -y skills add RonenMars/threadbase-mobile --skill ship-fastlane --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Ship Fastlane?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/ronenmars-ship-fastlane-threadbase-mobile)More formats (shields.io, HTML) on the badges page.
---
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 [`.claude/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).
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!