Skip to content
Back to skills

Eas Build And Submit

ASecurity

Configures EAS Build and Submit: eas.json profiles, remote build numbers, fingerprints, gated store submits. Use when editing eas.json, running eas build/submit, or a build fails. Not for local builds (ios-release-testflight) or OTA (ota-updates).

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 18, 2026
developmentgoshellnodetestinggitapi

Works with

  • cli
  • api

Security analysis

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

Pro scans all 19 files and shows the line behind each finding

Scanned October 18, 2026

npx -y skills add zakariaf/ReactNative-Skills --skill eas-build-and-submit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Eas Build And Submit?

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

Security grade badge for Eas Build And Submit
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/zakariaf-eas-build-and-submit/badge)](https://www.skillsdirectory.com/skills/zakariaf-eas-build-and-submit)

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: eas-build-and-submit
description: "Configures EAS Build and Submit: eas.json profiles, remote build numbers, fingerprints, gated store submits. Use when editing eas.json, running eas build/submit, or a build fails. Not for local builds (ios-release-testflight) or OTA (ota-updates)."
---

# EAS Build and Submit

An Expo app builds every variant on EAS from one checked `eas.json`, and a store build reaches
TestFlight and Google Play internal testing only after the gates passed, from the reviewed commit, with
a build number no store has seen and a fingerprint that matches the reviewed native inputs. Credentials
stay with EAS and the developer; the agent builds, proves and submits with the developer's word.

## Rules that must hold

1. **Nothing leaves the machine without the developer's word in this session.** A store `eas build`,
   every `eas submit`, `eas build:version:set` and any `eas credentials` change ask first. *Why:* each
   uploads, spends a build number or changes shared signing state, and none can be undone.
2. **eas.json is valid for the CLI it names.** `cli.version` is a floor `>= 24.10.0` (or newer), every key
   and value passes EAS CLI's own schema (`image` only inside `ios`/`android`, `node` a full version),
   and `extends` chains resolve. *Why:* EAS CLI refuses an invalid file only after a login, often in CI;
   the checker and `eas config --eas-json-only` catch it offline in seconds.
3. **Build numbers are remote and only go up.** `cli.appVersionSource` is `remote`, every store profile
   has `autoIncrement: true`, and the app config holds a literal `version: 'x.y.z'` (raised per store
   release) and no `ios.buildNumber` or `android.versionCode`. *Why:* EAS keeps one counter per app id,
   so two machines can never upload the same number (ITMS-90189, a used `versionCode`), and a number in
   the file would only mislead.
4. **A build is exactly a commit.** `cli.requireCommit` is `true`, and store builds start from a clean
   tree on the release branch. *Why:* without it EAS CLI copies uncommitted and untracked files into the
   build, so its `gitCommitHash` lies and the review covered other code.
5. **A profile per variant (and per simulator build), and only `production` is a store build.** `development`,
   `development-simulator`, `preview`, `preview-simulator` and `production` (templates/eas.json);
   the store profile builds an Android app bundle, signs, and never sets `developmentClient`,
   `withoutCredentials` or `ios.simulator`; internal profiles never build an AAB. *Why:* EAS defaults a
   profile without `distribution` to `store`, Play takes only app bundles, and testers cannot install an
   AAB from a link.
6. **Every build profile pins the project's Node.** `"node": "26.4.0"`, equal to `.nvmrc` (or an
   `eas-build-pre-install` script that upgrades npm). *Why:* the SDK 57 images ship npm 10.9.8, which the <!-- stack-pin-ignore -->
   `engines` field and the install policy refuse (`EBADENGINE`), and which ignores `min-release-age`.
7. **`submit.production` pins the production ids and reaches testers only.** `ios.ascAppId` (digits) and
   `ios.bundleIdentifier`, `android.applicationId`, never a `.dev`/`.preview` id; Android `track` is
   `internal` (or `alpha`/`beta`) with `releaseStatus` `completed` or `draft`. *Why:* a standalone
   `eas submit` evaluates the app config without the profile's env and would look up the development
   id; promotion and rollouts are store-console decisions (android-release-play).
8. **Credentials stay with EAS and the developer.** Managed credentials (`credentialsSource: remote`),
   `--freeze-credentials` on every `eas build` (EAS Workflows build jobs have no such flag), no `appleId` login and no literal key path in `eas.json`
   (EAS-stored keys or `$VARIABLES` from CI secrets); the agent never opens a `.p8`, `.p12`, keystore or
   service-account file. *Why:* a leaked key publishes apps in the developer's name, and a build that
   regenerates a certificate breaks every app that shares it.
9. **Gates first, then one named command.** An outward `eas build` runs after `npm run verify` or
   `scripts/eas/release-preflight.ts`, and an `eas submit` after `check-eas-build` on its record, in the
   same script or job, as a command, never only in a comment (an EAS Workflows `type: build` job
   `needs:` the job that ran verify, a `type: submit` job the job that ran the record check); every
   `eas build`/`eas submit` names `--profile` (a job: `params.profile`); EAS CLI is the global `eas`,
   never `npx eas-cli`; no `eas submit --latest`; `--auto-submit` (`-s`) only from the store profile and
   only for iOS; CI commands pass `--platform`, and `eas submit` `--non-interactive`. *Why:* `eas build`
   defaults to `production`, `--latest` submits whatever finished last, verify proves the code but not
   what EAS built, an auto-submitted Android bundle reaches Play before its gate read it, and
   `eas submit` stays interactive even in CI.
10. **Only a build whose record proves it is submitted, and only the file its gate read.**
    `check-eas-build` on `eas build:view <id> --json`: finished, store distribution, the production
    profile, the reviewed commit, the production id, the app config's version, a build number above
    every upload tag, and the fingerprint the preflight wrote. Then the record's artifact is downloaded
    to `build/ios-release/export/app.ipa` / `build/release-android/app-release.aab`, the stores' own
    gates read it (ios-release-testflight, android-release-play), and `eas submit --path <that file>`
    uploads exactly those bytes. *Why:* the record is the only evidence of what EAS built, the artifact
    gate the only evidence of what is inside it; a wrong variant or commit looks like success until
    testers open it.
11. **The fingerprint decides between a build and an update, and nothing hides native inputs from it.**
    Compare fingerprints of the same variant and platform (`eas fingerprint:compare --build-id <id>`);
    `.fingerprintignore` never hides `package.json`, the lockfile, the app config, `plugins/`, `patches/`
    or `node_modules/`, and `sourceSkips` never `ExpoConfigAll`. *Why:* a hidden native change ships as
    an update to binaries that lack the code, and the build-record check loses its proof.
12. **Stops are never retried.** Credentials errors, agreements, HTTP 401/403, missing app records, a
    used build number, a draft Play app, an `INVALID` processing state: one message with the step, the
    exact error and the one action. *Why:* retries lock accounts, burn numbers and hide the real cause.
13. **Every submitted build is tagged from its record.** `git tag -a v1.4.0+ios.14 -m "App Store build 14"`
    (and `+android.<n>`, "Play build N"), with the record's `appBuildVersion`, pushed only with the
    developer's word. *Why:* each TestFlight and Play build traces back to its commit, and the next
    record check compares against these numbers (git-commits-and-reporting owns the scheme).

## Workflow

1. **Choose the lane and list the human steps.** EAS is the default; a team that cannot use EAS takes the
   local lane (ios-release-testflight, android-release-play) and this skill's checker prints NOT
   APPLICABLE. Read [references/credentials-and-signing.md](references/credentials-and-signing.md) and
   ask the developer, one step per message, for what is missing: accounts, `eas init` (writes
   `extra.eas.projectId`), app records, signing and submit keys, `EXPO_TOKEN` for CI.
2. **Install EAS CLI** globally with the policy flags:
   `npm install -g --min-release-age=7 --ignore-scripts eas-cli@24.10.0`, never `npx eas-cli` in the app
   ([references/build-and-submit-runbook.md](references/build-and-submit-runbook.md), "Install the CLI").
3. **Write eas.json** from `templates/eas.json`: fill `__ASC_APP_ID__` (the Apple ID number of the app
   record) and `__APP_ID__` (the production id); keep an existing file's extra profiles and merge the
   variant wiring app-variants-and-environment owns. Read [references/eas-json.md](references/eas-json.md)
   for every key and the ownership table; leave `image` out (expo-sdk-upgrade's `auto` default).
   Validate offline: `eas config --eas-json-only --profile production --platform ios --json --non-interactive`.
4. **Fix the version fields** in `app.config.ts`: a literal `version`, no `ios.buildNumber` or
   `android.versionCode`. For an app with store history, ask the developer to run
   `eas build:version:set` per platform with the last store numbers
   ([references/versions-and-fingerprints.md](references/versions-and-fingerprints.md)).
5. **Copy the release templates**: `templates/scripts/eas/` (`eas-profile.ts`, `eas-commands.ts`,
   `release-plan.ts`, `release-preflight.ts` and the three tests) and, when missing,
   `templates/src/config/app-variant.ts` with its test (the canonical resolver). Run
   `npx jest scripts/eas --ci --maxWorkers=2`, `npm run typecheck` and `npx eslint scripts/eas --max-warnings 0`.
6. **Check the setup:** `node ${CLAUDE_SKILL_DIR}/scripts/check-eas-config.mjs .`; fix every `FAIL`
   line and rerun until `RESULT: PASS`. Then app-variants-and-environment's `check-variants` and
   expo-sdk-upgrade's `check-sdk-alignment` for the parts they own.
7. **Internal builds** (dev client, preview, simulator builds for E2E) need no preflight: the runbook's
   table has each command.
8. **A store release**, step by step from the runbook: `check-eas-config.mjs . --release`; then
   `node scripts/eas/release-preflight.ts --profile production --platform all --what-to-test "<text>"`,
   which prints the sequence; then ask (rule 1, shape in [examples/release-report.md](examples/release-report.md));
   then its build line, `eas build ... --json > dist/eas/builds.json` (no `--auto-submit`); then
   `node ${CLAUDE_SKILL_DIR}/scripts/check-eas-build.mjs . --build dist/eas/builds.json --platform ios`
   (and android); then its download and store-artifact gate lines (ios-release-testflight's
   `check-store-artifact`, android-release-play's `check-store-aab`); then its submit lines,
   `eas submit --profile production --platform <p> --path <gated file> --non-interactive` (What to Test
   on iOS); then tag and report. The preflight's `--auto-submit` (iOS only: the build submits itself,
   record checked afterwards) only when the developer explicitly agreed to skip the record check.
9. **Before an update instead of a build**, compare fingerprints
   ([references/versions-and-fingerprints.md](references/versions-and-fingerprints.md), "New build or
   update?") and hand the update to ota-updates.
10. **On any failure** find the row in [references/failure-playbook.md](references/failure-playbook.md):
    fix and rerun, or send the stop message and wait.

## Definition of done

- [ ] `eas.json` has the five profiles, remote versions, `requireCommit`, the CLI floor, `node` in every
      profile and `submit.production` with the production ids; `eas config --eas-json-only` accepts it.
- [ ] `app.config.ts` has a literal `version` and no build numbers; remote counters are initialised
      for an app with store history (or it is a new app).
- [ ] `scripts/eas/` is in place and its tests pass with the app's typecheck and lint.
- [ ] For a store release: the developer agreed in this session; the preflight passed; each submitted
      build's `check-eas-build` printed `RESULT: PASS`; each build is tagged; the report holds every
      RESULT line and nothing was retried after a stop.
- [ ] No key file was opened, copied or uploaded by the agent; `eas.json` holds no secret or key path.
- [ ] An app on the local lane prints `NOT APPLICABLE: no eas.json and nothing uses EAS ...`; an app
      whose EAS profiles are all internal while `release:ios`/`release:android` ships the store builds
      prints the `internal-only EAS: ...` note; there are no SKIP lines (both checkers are strict; a
      shared `base` profile others extend is judged through the profiles built from it).
- [ ] `node ${CLAUDE_SKILL_DIR}/scripts/check-eas-config.mjs .` prints `RESULT: PASS` (with `--release`
      before a store build)

## Anti-patterns

- **`eas build` with no `--profile`.** It silently builds `production`, spends a store number and, with
  `--auto-submit`, uploads. Name the profile every time.
- **`eas submit --latest`.** It takes whatever build finished last: a preview build, another branch.
  Submit the gated file with `--path` after `check-eas-build` and the store-artifact gate.
- **`eas build --platform all --auto-submit`.** The Android bundle reaches Play before any gate read
  it; build, check, gate, then `eas submit --path`.
- **`"appVersionSource": "local"` plus a script that bumps `buildNumber` and commits.** Two machines or a
  retried CI job upload the same number. Use remote versions.
- **`"image": "latest"`** (what `eas init`'s template writes). A new Xcode reaches the build without a
  commit; leave `image` out.
- **No `node` in the profiles "because it worked locally".** The worker's npm 10 refuses `engines` or, <!-- stack-pin-ignore -->
  worse, ignores the release-age policy.
- **Fixing a credentials error by switching to `credentialsSource: "local"` or an Apple ID login.** A
  stop was worked around and a key file was handled; send the stop message.
- **Reading the `.p8` or the keystore "to check it is there".** Ask the developer or read
  `eas credentials` output; never open the file.
- **`eas build:version:set` to a lower number to "reuse" a failed build's number.** The store refuses it;
  spent numbers stay spent.
- **Announcing "on TestFlight" when `eas build` returns.** Check the record, submit, then wait for
  Apple's processing.
- **`.fingerprintignore` with `package.json` or `**/node_modules/**` to make builds "reusable".** Native
  changes stop changing the fingerprint and updates reach binaries without the code.
- **`--auto-submit` from `preview` to put testers on TestFlight.** Preview is an internal build with the
  preview id; TestFlight takes store builds of the production id.

## Files in this skill

| File | What it is | Read/run when |
|---|---|---|
| [references/eas-json.md](references/eas-json.md) | Every profile and key with its reason, the extends merge, Node on EAS, images, the submit profile, the full schema table, offline validation, ownership | Workflow step 3; an `eas-schema`, `cli-version`, `store-profile`, `internal-profile`, `build-node` or `submit-profile` failure |
| [references/credentials-and-signing.md](references/credentials-and-signing.md) | What signs what, the human setup steps, frozen credentials, submit keys, local credentials, CI, never-list, leak response | Workflow step 1; a `submit-credentials` failure; any credentials error |
| [references/versions-and-fingerprints.md](references/versions-and-fingerprints.md) | Version policy, remote counters per app id, initialising them, tags, what the fingerprint hashes (measured), how EAS records it, build or update, test recipes | Workflow steps 4 and 9; a `version-source`, `config-versions` or `fingerprint-config` failure |
| [references/build-and-submit-runbook.md](references/build-and-submit-runbook.md) | Installing the CLI, internal builds, the store release step by step, What to Test, groups and tracks, after submission, `--local`, workflows, what needs the developer | Workflow steps 2, 7 and 8 |
| [references/failure-playbook.md](references/failure-playbook.md) | Symptom, cause, fix or stop for every known local, worker, version, submission and processing failure | Workflow step 10 |
| [examples/release-report.md](examples/release-report.md) | The ask, the evidence message and a stop message for a Tasks release, and bad versions | Workflow step 8 |
| `templates/eas.json` | The five build profiles and `submit.production` (`__ASC_APP_ID__`, `__APP_ID__`) | Workflow step 3 |
| `templates/scripts/eas/eas-profile.ts` | Resolves a build profile like EAS CLI; store-profile problems; pinned submit ids | Workflow step 5 |
| `templates/scripts/eas/eas-commands.ts` | `eas build` / `eas submit --path` argument lists, the gated artifact paths, the release sequence, the What to Test limit, upload tags, a shell-safe line | Workflow step 5 |
| `templates/scripts/eas/release-plan.ts` | The preflight's pure decisions: tree, branch, profile, version above the last release, ids | Workflow step 5 |
| `templates/scripts/eas/release-preflight.ts` | The gates-first CLI: tree and profile, `npm run verify`, the app config, fingerprints to `dist/eas/`, the printed release sequence (build, record checks, download and store-artifact gates, `--path` submits; iOS `--auto-submit` only on request); unattended under `CI=true`, non-zero on any failed gate | Workflow steps 5 and 8 |
| `templates/scripts/eas/eas-profile.test.ts` | Tests of the profile resolution and the store-profile rules | With the module |
| `templates/scripts/eas/eas-commands.test.ts` | Tests of the command lines, the release sequence (no `--auto-submit` by default, never for Android), the What to Test limit and upload tags | With the module |
| `templates/scripts/eas/release-plan.test.ts` | Tests of the preflight's decisions | With the module |
| `templates/src/config/app-variant.ts` | The canonical variant resolver the profile module reads (synced from the library; do not edit here) | Workflow step 5, when missing |
| `templates/src/config/app-variant.test.ts` | Its tests (synced; do not edit here) | With the resolver |
| `scripts/check-eas-config.mjs` | Checks eas.json, the app config's version fields, commands and their gates, the preflight and the fingerprint inputs (`--release`: ship mode) | Workflow steps 6 and 8 |
| `scripts/check-eas-build.mjs` | The ship gate on a build record before submission | Workflow step 8 |
| `scripts/lib/eas-json.mjs` | eas.json reader, EAS's schema and extends resolution, offline | Only when changing a checker |
| `scripts/lib/repo-facts.mjs` | App config facts, Node pins, eas commands in scripts and workflows | Only when changing a checker |
| `scripts/lib/ship-placeholders.mjs` | The placeholder values a ship gate refuses (synced from the library; do not edit here) | Only when changing a checker |
| `scripts/check-lib.mjs` | Shared checker library (synced from the library; do not edit here) | Never by hand |
| `scripts/selftest.mjs` | Runs both checkers against the fixtures (the tags suite builds a throwaway git repo per case from its `TAGS.txt`) | After changing a checker or a template |
| `tests/bases/` | The Tasks base app, build records and fingerprints, and decoys under `.agents/skills/`, `skills/` and a vendored SKILL.md folder | When adding a fixture |
| `tests/fixtures/` | Overlay cases per checker: good, one bad case per rule, pass and error cases; `eas-build-tags/` reads upload tags of an app at `apps/mobile` | When adding a rule |
| `assets/eas-facts.json` | The verified CLI floor, image Node and npm, the project Node, the What to Test limit | When re-verifying for a new CLI or SDK |
| `assets/shared.json` | The shared files this skill syncs in | When adding a shared file |
| `assets/template-placeholders.json` | Values for the template placeholders in template checks | When a template gains a placeholder |

## Related skills

- `app-variants-and-environment` - the variant env and `environment` of each profile, secrets in EAS environments.
- `expo-sdk-upgrade` - EAS images (`auto` by default, never `latest`) and the SDK move.
- `dependency-management` - the install on EAS (`npm ci`, the lockfile, `NPM_CONFIG_*`), the global EAS CLI install.
- `ota-updates` - channels per profile, runtime versions from fingerprints, `eas update`.
- `ios-release-testflight` - signing details, the store-artifact gate on the `.ipa`, and the local lane.
- `android-release-play` - Play App Signing, the AAB gate, tracks beyond internal, staged rollout.
- `ci-pipeline-and-gates` - the CI jobs that run these commands with `EXPO_TOKEN`.
- `git-commits-and-reporting` - upload and release tags, the evidence message, the stop list.
- `privacy-and-store-declarations` - privacy manifest and store answers before a submission.
- `ios-simulator-build`, `android-emulator-build`, `e2e-maestro` - local builds and E2E on named devices.
- Expo's official `eas-app-stores` and `eas-workflows` skills - API references for EAS Submit and EAS Workflows.

Files in this skill

  • SKILL.md19.9 KB
  • assets/eas-facts.json762 B
  • assets/shared.json383 B
  • assets/template-placeholders.json76 B
  • examples/release-report.md2.3 KB
  • references/build-and-submit-runbook.md17.8 KB
  • references/credentials-and-signing.md8.1 KB
  • references/eas-json.md15.4 KB
  • references/failure-playbook.md7.9 KB
  • references/versions-and-fingerprints.md9.6 KB
  • scripts/check-eas-build.mjs12.6 KB
  • scripts/check-eas-config.mjs34.2 KB
  • scripts/lib/eas-json.mjs11.1 KB
  • scripts/lib/repo-facts.mjs21.6 KB
  • scripts/lib/ship-placeholders.mjs8 KB
  • scripts/selftest.mjs2.1 KB
  • templates/eas.json1.4 KB
  • templates/scripts/eas/eas-commands.test.ts8 KB
  • templates/scripts/eas/eas-commands.ts9.9 KB

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.