Skip to content
Web apps · Workflow

Expo EAS Update without shipping native changes over the air

Checks whether an Expo change can ship as an EAS Update or needs a new build, using the native fingerprint; preview by CI, production rollouts by a person.

Verified install Not agent-tested v1.0.0 MIT updated exporeact-nativetypescriptgithub-actions
Equips
  • 1 1 Rules
  • 1 1 Skills
  • – 0 MCP

An EAS Update swaps the JavaScript inside a build, but it cannot add native code. An agent that installs expo-camera and then runs eas update ships a crash to every user on that build. This loadout gives your agent a check it runs before any update. It records the native fingerprint when a store build is cut, then compares the current project against it with expo-updates' own fingerprint tool. JavaScript and version bumps pass. A new native module, config plugin or permission fails, and the output names what changed. A config check makes sure runtimeVersion uses the fingerprint policy, updates.url matches the project and every build profile has its own channel. It validates eas.json with Expo's own parser. CI publishes to preview only. Production goes out as a staged rollout run by a person. The verifier runs it on Expo SDK 58 with expo-updates 58.0.17.

What's in it

  • OTA-safe or needs a new build, from the expo-updates native fingerprint
  • Names what changed: added native modules, config, autolinking
  • Fingerprint runtime policy, matching updates.url, one channel per build profile
  • CI publishes to preview only; production is a person's staged rollout
  • Verified on Expo SDK 58, expo-updates 58.0.17 and @expo/eas-json 25.1.1

You'll need

  • Tools: node, npm
Level 1 · automated

Verification

Verified install

Checked at commit 08c3710 · 9 passed, 0 failed, 1 not applicable

CheckResultWhat we found
Listinglisting.yaml matches the schema and declares every file in the folder. Pass
Schema valid; 8 files declared, none missing or extra
  • tested.status: untested (agent tests are separate from Level 1)
Files parseJSON parses; SKILL.md and .mdc front matter is valid YAML with the fields each agent needs; code fences are closed. Pass All 8 files parse; front matter valid
AdaptersCLAUDE.md imports @AGENTS.md, GEMINI.md imports @./AGENTS.md, and both match the generator. Pass
CLAUDE.md and GEMINI.md import AGENTS.md and are up to date
  • CLAUDE.md: matches the generator
  • GEMINI.md: matches the generator
Install pathsEvery file lands where each agent documents it, with no collisions, and the laid-out project is re-discovered correctly. Pass
Correct paths for all 6 agents; layouts re-discovered
  • Claude Code: 7 files, 1 skill in .claude/skills/
  • Codex CLI: 6 files, 1 skill in .agents/skills/
  • Cursor: 6 files, 1 skill in .agents/skills/
  • Gemini CLI: 7 files, 1 skill in .agents/skills/
  • Antigravity CLI: 6 files, 1 skill in .agents/skills/
  • GitHub Copilot: 6 files, 1 skill in .agents/skills/
MCP configmcp.json is structurally valid, converts to each agent's format, declares no literal secrets, and referenced packages exist on npm/PyPI. Not applicable No MCP servers in this loadout
Safety scanThe intake scanner finds no credentials, risky shell, hidden Unicode or prompt-injection wording, and the listing's network flag is honest. Pass
Clean: no credentials, risky shell or hidden text
  • Scanner intake-precheck/1: verdict clean; network no (listing says yes)
ReferencesLinks and file paths between the loadout's files resolve, including for each agent's installed layout. Pass 4 references between files resolve for every agent
Size limitsFile count and sizes are within the submission limits; SKILL.md bodies stay under 500 lines. Pass
8 files, 14.7 KB total; largest skills/eas-update-release/scripts/ota_check.mjs 6.2 KB
  • Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule
LicenceA recognised SPDX licence, with the licence text available. Pass MIT, by Agent Loadout; text in the repository LICENSE
Starter buildA minimal fixture project with the loadout applied installs and builds or type-checks, and the loadout's own code examples compile. Pass
The eas-update-release skill's app.json and eas.json pass ota_check config in an Expo SDK 58 app (expo-updates 58.0.17, @expo/eas-json 25.1.1); a JS edit and a version bump are OTA-safe, adding expo-haptics needs a new build, a profile with no channel and a misspelt key are caught, and the bundle ex
  • Applied 13 loadout files at every agent's paths
  • ✓ npm ci (pinned Expo SDK 58): npm ci --no-audit --no-fund --ignore-scripts
  • ✓ place the skill's app.json and eas.json, check the update config: bash -c R=.agents/skills/eas-update-release/references; cp $R/app.json $R/eas.json . && node .agents/skills/eas-update-release/scripts/ota_check.mjs config
  • ✓ typecheck: npx tsc --noEmit
  • ✓ record the build fingerprint: node .agents/skills/eas-update-release/scripts/ota_check.mjs record
  • ✓ a JS change and a version bump are OTA-safe: bash -c sed -i 's/Release notes v1/Release notes v2/' App.tsx && sed -i 's/"version": "1.4.0"/"version": "1.4.1"/' app.json && grep -q 1.4.1 app.json && node .agents/skills/eas-update-release/scripts/ota_check.mjs compare
  • ✓ the bundle exports for both platforms: npx expo export --platform ios --platform android --output-dir dist
  • ✓ adding a native module needs a new build: bash -c npm install expo-haptics@58.0.7 --no-audit --no-fund --ignore-scripts >/dev/null && node .agents/skills/eas-update-release/scripts/ota_check.mjs compare; test $? -eq 1 && echo blocked
  • ✓ a profile with no channel and a misspelt key are caught: bash -c cp eas.json eas.good && node -e 'const f=require("fs");const d=JSON.parse(f.readFileSync("eas.json"));delete d.build.preview.channel;f.writeFileSync("eas.json",JSON.stringify(d))' && node .agents/skills/eas-update-release/scripts/ota_check.mjs config; a=$?; node -e 'const f=require("fs");const d=JSON.parse(f.readFileSync("eas.good"));d.build.production.autoIncrment=true;f.writeFileSync("eas.json",JSON.stringify(d))' && node .agen
  • Covers: runtimeVersion fingerprint policy, updates.url matching the project ID, a channel per build profile
  • Covers: eas.json validated with Expo's own @expo/eas-json parser, including extends
  • Covers: Native fingerprint recorded at build time and compared before an update: JS and version bumps pass, a new native module fails
  • Covers: expo export bundles for iOS and Android, the same step eas update runs

What this means: a script checked the files themselves: they parse, they install where each agent looks, any MCP config is sound, the safety scan is clean, references between files resolve, and, where a free and repeatable fixture exists, a small starter project builds with the loadout applied. No AI agent was run. Per-agent results ("tested with agent") are separate and stay untested until an agent actually runs.

Install

Choose your agent. Run the commands from your project root, or download a zip already laid out in the right folders.

Claude Code reads CLAUDE.md (imports AGENTS.md with @AGENTS.md) and skills from .claude/skills/.

Download zip
curl -fsSL --create-dirs -o AGENTS.md https://loadout.jnw-it.com/raw/expo-eas-update/1.0.0/AGENTS.md
curl -fsSL --create-dirs -o CLAUDE.md https://loadout.jnw-it.com/raw/expo-eas-update/1.0.0/CLAUDE.md
curl -fsSL --create-dirs -o .claude/skills/eas-update-release/SKILL.md https://loadout.jnw-it.com/raw/expo-eas-update/1.0.0/skills/eas-update-release/SKILL.md
curl -fsSL --create-dirs -o .claude/skills/eas-update-release/scripts/ota_check.mjs https://loadout.jnw-it.com/raw/expo-eas-update/1.0.0/skills/eas-update-release/scripts/ota_check.mjs
curl -fsSL --create-dirs -o .claude/skills/eas-update-release/references/app.json https://loadout.jnw-it.com/raw/expo-eas-update/1.0.0/skills/eas-update-release/references/app.json
curl -fsSL --create-dirs -o .claude/skills/eas-update-release/references/eas.json https://loadout.jnw-it.com/raw/expo-eas-update/1.0.0/skills/eas-update-release/references/eas.json
curl -fsSL --create-dirs -o .claude/skills/eas-update-release/references/eas-update-preview.yml https://loadout.jnw-it.com/raw/expo-eas-update/1.0.0/skills/eas-update-release/references/eas-update-preview.yml
FileGoes to
AGENTS.mdAGENTS.md
CLAUDE.mdCLAUDE.md
skills/eas-update-release/SKILL.md.claude/skills/eas-update-release/SKILL.md
skills/eas-update-release/scripts/ota_check.mjs.claude/skills/eas-update-release/scripts/ota_check.mjs
skills/eas-update-release/references/app.json.claude/skills/eas-update-release/references/app.json
skills/eas-update-release/references/eas.json.claude/skills/eas-update-release/references/eas.json
skills/eas-update-release/references/eas-update-preview.yml.claude/skills/eas-update-release/references/eas-update-preview.yml

Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want.

Files

Rules AGENTS.md Rules for an Expo app updated over the air with EAS Update, where native changes always get a new build. 25 lines
Download Raw
# AGENTS.md: Expo app shipped with EAS Update

An Expo SDK 58 app (React Native 0.88) built with EAS Build and updated over the air with EAS Update. Check
`package.json` for exact versions, and add packages with `npx expo install` so versions match the SDK.

## Commands
- Typecheck: `npx tsc --noEmit`.
- Bundle check: `npx expo export --platform ios --platform android --output-dir dist`.
- Update config check: `node .agents/skills/eas-update-release/scripts/ota_check.mjs config`.
- OTA safety: `node .agents/skills/eas-update-release/scripts/ota_check.mjs compare` (against the fingerprint
  recorded when the current store build was cut).

## Rules
- Use the `eas-update-release` skill before any change is published as an update, and after adding or upgrading
  a package or editing app.json, app.config or eas.json.
- `runtimeVersion` uses the fingerprint policy. If `compare` reports a native change, the change needs a new
  build: say so, do not publish an update.
- Agents may publish to the `preview` channel through CI. Production updates, rollouts and rollbacks are run by
  a person.
- Never run `eas update --auto`, `--force-end-active-rollout`, `eas update:rollback` or `eas channel:*`.
- Never edit or regenerate the committed build fingerprint to make a check pass. It is recorded when a build is cut.
- `EXPO_TOKEN` and other secrets live in CI secrets or EAS environment variables, never in the repository.
- Code that depends on a new native module checks it exists before calling it, so an older build degrades
  instead of crashing.
Adapter CLAUDE.md Imports AGENTS.md for Claude Code. 11 lines
Download Raw
# CLAUDE.md

@AGENTS.md

## Claude Code notes

- Skills from this loadout live in `.claude/skills/`: `eas-update-release`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "Expo EAS Update without shipping native changes over the air" v1.0.0 — https://loadout.jnw-it.com/loadouts/expo-eas-update/ -->
Adapter GEMINI.md Imports AGENTS.md for Gemini CLI. 11 lines
Download Raw
# GEMINI.md

@./AGENTS.md

## Gemini CLI notes

- Skills from this loadout live in `.agents/skills/`: `eas-update-release`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "Expo EAS Update without shipping native changes over the air" v1.0.0 — https://loadout.jnw-it.com/loadouts/expo-eas-update/ -->
Skill skills/eas-update-release/SKILL.md Config, the OTA-safe check, and who publishes where. 82 lines
Download Raw
---
name: eas-update-release
description: Decide whether an Expo change can ship as an EAS Update or needs a new store build, check the update config, and prepare a staged rollout. Use before publishing with eas update, after adding or upgrading a package, or when editing app.json, app.config or eas.json.
---

# Shipping with EAS Update

An EAS Update replaces the JavaScript and assets inside a build. It cannot change native code. If an update
calls a native module the build does not contain, it crashes on users' phones, and `expo-updates` falls back
to the previous update only if it notices. This skill keeps that from happening.

## 1. Config once: fingerprint runtime version and one channel per profile

```bash
node .agents/skills/eas-update-release/scripts/ota_check.mjs config
```

It reads the resolved app config (`npx expo config --type public`) and checks:

- `runtimeVersion` is `{ "policy": "fingerprint" }` (top level or per platform). The fingerprint changes
  whenever anything that affects the native layer changes. A manual string or `appVersion` policy only works
  if someone remembers to bump it, so the script notes it.
- `updates.url` is `https://u.expo.dev/<projectId>` and matches `extra.eas.projectId`.
- `eas.json` parses with Expo's own validator (`@expo/eas-json`), and every non-development build profile has
  its own `channel`, including one called `production`.

Start from `references/app.json` and `references/eas.json`.

## 2. Every change: is it OTA-safe?

When a store build is cut, record its native fingerprint and commit the file:

```bash
node .agents/skills/eas-update-release/scripts/ota_check.mjs record      # writes .eas/build-fingerprint.json
```

Before any update, compare:

```bash
node .agents/skills/eas-update-release/scripts/ota_check.mjs compare
```

- `OTA-safe` (exit 0): only JavaScript, assets or non-native config changed. An update can carry it.
- `needs a new build` (exit 1): it lists what changed, for example `added: node_modules/expo-camera/package.json`
  or `changed: expoConfig`. Do not publish an update. Say a new build is needed and why.

Typical "needs a build" changes: adding or upgrading any package with native code (`npx expo install` of an
`expo-*` module), a new config plugin, permissions, icons and splash, bundle identifiers, an SDK upgrade.
A version bump alone does not change the fingerprint.

With an Expo account the same check is `eas fingerprint:compare --build-id <id>`. The script is for CI and
for machines without a token.

## 3. Publishing: preview by the pipeline, production by a person

- Preview: CI publishes to the `preview` channel when `compare` passes. See
  `references/eas-update-preview.yml`. `--environment` is required for SDK 55 and later.
- Production, done by a person after testing on a preview build, as a staged rollout:

  ```bash
  eas update:republish --branch preview --destination-channel production --rollout-percentage 10 -m "..."
  eas update:edit                      # raise the percentage once crash-free rates hold
  eas update:revert-update-rollout     # or back out
  ```

- One rollout per branch and runtime at a time. Never pass `--force-end-active-rollout` to get past one.
- Roll back with `eas update:rollback`. A person runs it; it affects every user on that runtime.

## What the agent does not do

- No `eas update` to the `production` channel or branch, no `--auto` (it publishes to a branch named after
  the git branch), no `update:rollback`, `channel:edit` or `channel:rollout`.
- No publishing when `compare` says a build is needed, and no editing the baseline file to make it pass.
- No `EXPO_TOKEN` in files, logs or commands. It lives in CI secrets only.

## Before you call it done

1. `npx tsc --noEmit` is clean.
2. `npx expo export --platform ios --platform android --output-dir dist` succeeds (the same bundle step an
   update runs).
3. `ota_check.mjs compare` result is stated in the summary, with the changed sources if a build is needed.
Skill file skills/eas-update-release/scripts/ota_check.mjs config, record and compare: fingerprint and update config checks. 114 lines
Download Raw
#!/usr/bin/env node
// Decide whether a change can ship as an EAS Update or needs a new store build.
// Uses the project's own expo-updates CLI (fingerprint:generate) and app config; no Expo account needed.
//
//   node ota_check.mjs config                 check app config + eas.json are set up for safe updates
//   node ota_check.mjs record  [file]         save the current native fingerprint (run when a build is cut)
//   node ota_check.mjs compare [file]         exit 0 "OTA-safe" if native fingerprint unchanged, 1 if a build is needed
//
// file defaults to .eas/build-fingerprint.json. Platforms: ios and android (AL_PLATFORMS to override).
import fs from "node:fs";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { createRequire } from "node:module";

const [cmd = "", fileArg] = process.argv.slice(2);
const file = fileArg ?? ".eas/build-fingerprint.json";
const platforms = (process.env.AL_PLATFORMS ?? "ios,android").split(",").map((s) => s.trim()).filter(Boolean);
const npx = process.platform === "win32" ? "npx.cmd" : "npx";

function runJson(args) {
  const r = spawnSync(npx, ["--no-install", ...args], { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
  if (r.status !== 0) {
    console.error(`failed: npx ${args.join(" ")}\n${(r.stderr || r.stdout || "").trim().slice(0, 2000)}`);
    process.exit(2);
  }
  const out = r.stdout.slice(r.stdout.indexOf("{"));
  return JSON.parse(out);
}

const keyOf = (s) => s.id ?? s.filePath ?? `${s.type}:${s.name ?? "?"}`;

function fingerprint(platform) {
  const fp = runJson(["expo-updates", "fingerprint:generate", "--platform", platform]);
  return { hash: fp.hash, sources: Object.fromEntries(fp.sources.map((s) => [keyOf(s), s.hash])) };
}

async function checkConfig() {
  const problems = [];
  const notes = [];
  const cfg = runJson(["expo", "config", "--json", "--type", "public"]);
  const rv = cfg.runtimeVersion;
  for (const p of platforms) {
    const prv = cfg[p]?.runtimeVersion ?? rv;
    if (prv === undefined) problems.push(`${p}: no runtimeVersion; set { "policy": "fingerprint" }`);
    else if (typeof prv === "object" && prv.policy !== "fingerprint") notes.push(`${p}: runtimeVersion policy "${prv.policy}" relies on people bumping versions when native code changes; "fingerprint" does it for you`);
    else if (typeof prv === "string") notes.push(`${p}: runtimeVersion "${prv}" is manual; bump it with every native change or switch to { "policy": "fingerprint" }`);
  }
  const url = cfg.updates?.url;
  if (!url || !/^https:\/\/u\.expo\.dev\/[0-9a-f-]{36}$/.test(url)) problems.push(`updates.url is ${url ? `"${url}"` : "missing"}; expected https://u.expo.dev/<projectId> (run eas update:configure once)`);
  const projectId = cfg.extra?.eas?.projectId;
  if (url && projectId && !url.endsWith(projectId)) problems.push(`updates.url does not match extra.eas.projectId (${projectId})`);
  if (cfg.updates?.enabled === false) problems.push("updates.enabled is false: builds will never download an update");

  if (!fs.existsSync("eas.json")) problems.push("no eas.json");
  else {
    // Validate and resolve "extends" with Expo's own eas.json parser (the @expo/eas-json dev dependency).
    let easJson;
    try { easJson = createRequire(path.resolve("package.json"))("@expo/eas-json"); }
    catch { problems.push("@expo/eas-json is not installed: add it as a dev dependency pinned to your eas-cli version"); }
    if (easJson) {
      const { EasJsonAccessor, EasJsonUtils } = easJson;
      const acc = EasJsonAccessor.fromProjectPath(process.cwd());
      const channels = new Map();
      try {
        for (const name of await EasJsonUtils.getBuildProfileNamesAsync(acc)) {
          const prof = await EasJsonUtils.getBuildProfileAsync(acc, platforms[0], name);
          if (prof.developmentClient) continue;
          if (!prof.channel) { problems.push(`eas.json build.${name}: no channel, so its builds cannot receive updates`); continue; }
          if (channels.has(prof.channel)) problems.push(`eas.json build.${name} and build.${channels.get(prof.channel)} share channel "${prof.channel}"`);
          channels.set(prof.channel, name);
        }
        if (!channels.has("production")) problems.push('eas.json: no build profile on channel "production"');
      } catch (e) {
        problems.push(e.message.replace(/\n/g, " "));
      }
    }
  }
  for (const n of notes) console.log(`note: ${n}`);
  for (const p of problems) console.log(`problem: ${p}`);
  if (problems.length) { console.log(`config: ${problems.length} problem(s)`); process.exit(1); }
  console.log(`config: ok (runtimeVersion ${JSON.stringify(rv)}, channels checked)`);
}

if (cmd === "config") await checkConfig();
else if (cmd === "record") {
  const data = { recordedAt: new Date().toISOString(), platforms: Object.fromEntries(platforms.map((p) => [p, fingerprint(p)])) };
  fs.mkdirSync(path.dirname(file), { recursive: true });
  fs.writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`);
  for (const p of platforms) console.log(`recorded ${p}: ${data.platforms[p].hash}`);
} else if (cmd === "compare") {
  if (!fs.existsSync(file)) { console.log(`no baseline at ${file}: run "record" when you cut the build`); process.exit(2); }
  const base = JSON.parse(fs.readFileSync(file, "utf8")).platforms;
  let changed = false;
  for (const p of platforms) {
    const now = fingerprint(p);
    const was = base[p];
    if (!was) { console.log(`${p}: no baseline recorded`); changed = true; continue; }
    if (now.hash === was.hash) { console.log(`${p}: unchanged (${now.hash})`); continue; }
    changed = true;
    console.log(`${p}: native fingerprint changed ${was.hash} -> ${now.hash}`);
    const keys = new Set([...Object.keys(was.sources), ...Object.keys(now.sources)]);
    for (const k of [...keys].sort()) {
      if (!(k in was.sources)) console.log(`  added: ${k}`);
      else if (!(k in now.sources)) console.log(`  removed: ${k}`);
      else if (was.sources[k] !== now.sources[k]) console.log(`  changed: ${k}`);
    }
  }
  if (changed) { console.log("needs a new build: do not ship this as an EAS Update"); process.exit(1); }
  console.log("OTA-safe: native layer unchanged, an EAS Update can carry this change");
} else {
  console.log("usage: ota_check.mjs config | record [file] | compare [file]");
  process.exit(2);
}
Skill file skills/eas-update-release/references/app.json App config with the fingerprint policy and updates.url. 19 lines
Download Raw
{
  "expo": {
    "name": "Release Notes",
    "slug": "release-notes",
    "version": "1.4.0",
    "owner": "acme",
    "ios": { "bundleIdentifier": "com.acme.releasenotes" },
    "android": { "package": "com.acme.releasenotes" },
    "runtimeVersion": { "policy": "fingerprint" },
    "updates": {
      "url": "https://u.expo.dev/00000000-0000-4000-8000-000000000000",
      "checkAutomatically": "ON_LOAD",
      "fallbackToCacheTimeout": 0
    },
    "extra": { "eas": { "projectId": "00000000-0000-4000-8000-000000000000" } },
    "plugins": ["expo-updates"]
  }
}
Skill file skills/eas-update-release/references/eas.json Build profiles with one channel each. 24 lines
Download Raw
{
  "cli": {
    "version": ">= 25.1.1",
    "appVersionSource": "remote"
  },
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal",
      "channel": "preview"
    },
    "production": {
      "autoIncrement": true,
      "channel": "production"
    }
  },
  "submit": {
    "production": {}
  }
}
Skill file skills/eas-update-release/references/eas-update-preview.yml GitHub workflow: publish to preview only when OTA-safe. 43 lines
Download Raw
# .github/workflows/eas-update-preview.yml
# On every push to main: typecheck, export the JS bundle, and publish an EAS Update to the
# preview channel ONLY if the native fingerprint matches the last recorded build.
# Production is promoted by a person (see the skill), never from this workflow.
name: EAS Update (preview)

on:
  push:
    branches: [main]

permissions:
  contents: read

concurrency:
  group: eas-update-preview
  cancel-in-progress: false

jobs:
  update:
    runs-on: ubuntu-24.04
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npx tsc --noEmit
      - name: Native fingerprint unchanged since the last build
        run: node .agents/skills/eas-update-release/scripts/ota_check.mjs compare
      - name: Bundle builds
        run: npx expo export --platform ios --platform android --output-dir dist
      - name: Publish to preview
        env:
          EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
          # Passed through the environment, never interpolated into the script.
          COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
        run: >
          npx --yes eas-cli@25.1.1 update
          --channel preview --environment preview
          --message "$COMMIT_MESSAGE" --non-interactive

All files: source zip · Licence MIT · By Agent Loadout · First listed

Credits

Public work that inspired this loadout. The wording here is our own; follow the links for the originals.

Installing and testing

How do I install Expo EAS Update without shipping native changes over the air in Claude Code?

From your project root, put AGENTS.md, CLAUDE.md, skills/eas-update-release/SKILL.md at .claude/skills/eas-update-release/SKILL.md, skills/eas-update-release/scripts/ota_check.mjs at .claude/skills/eas-update-release/scripts/ota_check.mjs, skills/eas-update-release/references/app.json at .claude/skills/eas-update-release/references/app.json, skills/eas-update-release/references/eas.json at .claude/skills/eas-update-release/references/eas.json, skills/eas-update-release/references/eas-update-preview.yml at .claude/skills/eas-update-release/references/eas-update-preview.yml. The "Claude Code" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/expo-eas-update/claude-code.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Expo EAS Update without shipping native changes over the air in Codex CLI?

From your project root, put AGENTS.md, skills/eas-update-release/SKILL.md at .agents/skills/eas-update-release/SKILL.md, skills/eas-update-release/scripts/ota_check.mjs at .agents/skills/eas-update-release/scripts/ota_check.mjs, skills/eas-update-release/references/app.json at .agents/skills/eas-update-release/references/app.json, skills/eas-update-release/references/eas.json at .agents/skills/eas-update-release/references/eas.json, skills/eas-update-release/references/eas-update-preview.yml at .agents/skills/eas-update-release/references/eas-update-preview.yml. The "Codex CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/expo-eas-update/codex.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Expo EAS Update without shipping native changes over the air in Cursor?

From your project root, put AGENTS.md, skills/eas-update-release/SKILL.md at .agents/skills/eas-update-release/SKILL.md, skills/eas-update-release/scripts/ota_check.mjs at .agents/skills/eas-update-release/scripts/ota_check.mjs, skills/eas-update-release/references/app.json at .agents/skills/eas-update-release/references/app.json, skills/eas-update-release/references/eas.json at .agents/skills/eas-update-release/references/eas.json, skills/eas-update-release/references/eas-update-preview.yml at .agents/skills/eas-update-release/references/eas-update-preview.yml. The "Cursor" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/expo-eas-update/cursor.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Expo EAS Update without shipping native changes over the air in Gemini CLI?

From your project root, put AGENTS.md, GEMINI.md, skills/eas-update-release/SKILL.md at .agents/skills/eas-update-release/SKILL.md, skills/eas-update-release/scripts/ota_check.mjs at .agents/skills/eas-update-release/scripts/ota_check.mjs, skills/eas-update-release/references/app.json at .agents/skills/eas-update-release/references/app.json, skills/eas-update-release/references/eas.json at .agents/skills/eas-update-release/references/eas.json, skills/eas-update-release/references/eas-update-preview.yml at .agents/skills/eas-update-release/references/eas-update-preview.yml. The "Gemini CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/expo-eas-update/gemini-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Expo EAS Update without shipping native changes over the air in Antigravity CLI?

From your project root, put AGENTS.md, skills/eas-update-release/SKILL.md at .agents/skills/eas-update-release/SKILL.md, skills/eas-update-release/scripts/ota_check.mjs at .agents/skills/eas-update-release/scripts/ota_check.mjs, skills/eas-update-release/references/app.json at .agents/skills/eas-update-release/references/app.json, skills/eas-update-release/references/eas.json at .agents/skills/eas-update-release/references/eas.json, skills/eas-update-release/references/eas-update-preview.yml at .agents/skills/eas-update-release/references/eas-update-preview.yml. The "Antigravity CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/expo-eas-update/antigravity-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Expo EAS Update without shipping native changes over the air in GitHub Copilot?

From your project root, put AGENTS.md, skills/eas-update-release/SKILL.md at .agents/skills/eas-update-release/SKILL.md, skills/eas-update-release/scripts/ota_check.mjs at .agents/skills/eas-update-release/scripts/ota_check.mjs, skills/eas-update-release/references/app.json at .agents/skills/eas-update-release/references/app.json, skills/eas-update-release/references/eas.json at .agents/skills/eas-update-release/references/eas.json, skills/eas-update-release/references/eas-update-preview.yml at .agents/skills/eas-update-release/references/eas-update-preview.yml. The "GitHub Copilot" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/expo-eas-update/copilot.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

Has Expo EAS Update without shipping native changes over the air been tested?

It passed the Level 1 automated install checks on 2026-10-17: the files parse, install to each agent's documented paths, and pass the safety scan. No AI agent has run it yet, so no agent pass or fail is claimed.

More for Claude Code, Codex, Cursor, Gemini CLI, Antigravity CLI and GitHub Copilot.