Skip to content

[agent-cli] The OTA verdict keeps expo config on every run, and skips it for a project with no app config - #70

Merged
Kudo merged 9 commits into
kudo/status-1-eas-none-cachefrom
kudo/status-2-ota-cache
Oct 19, 2026
Merged

Kudo merged 9 commits into
kudo/status-1-eas-none-cachefrom
kudo/status-2-ota-cache

Conversation

@expo-tuft

@expo-tuft expo-tuft Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Stack 2/4 of the plan to remove status --explain. Based on #69.

Why

The OTA verdict runs expo config --json --type public to learn the runtimeVersion. Once --explain goes away (#71), every status run pays for that call.

We tried reading a static app.json as a file and caching a dynamic config's answer. Review showed that this is not safe:

  • A dynamic config can read env vars and .env files, and no file stamp sees them. A cache can report an old runtimeVersion.
  • A static app.json can list config plugins, and expo config --type public runs them. A plugin can set runtimeVersion, so the file alone is not the answer.

So this PR keeps expo config as the source on every run, with no cache. An env-aware cache can come later in its own PR.

How

  • resolveRuntimeVersionAsync still runs expo config --json --type public first, for static and dynamic configs.
  • A project with no app config file is not evaluated, because npx expo could fetch a package for it. It reports source: null.
  • When the subprocess fails, the answer is source: null and the verdict is unknown. The static file is not a fallback, for the same reasons as above.
  • LLP 0011 says why the answer is not cached. LLP 0023 says --no-fingerprint-cache also refuses the EAS answer record.

Test Plan

  • Unit src/impact/__tests__/runtimeVersion-test.ts: expo config answers for static and dynamic configs with the pinned argv; a static app.json is evaluated too; no app config file spawns nothing; source: null when the subprocess fails (even beside a static runtimeVersion), prints something unparsable, or fails on a dynamic config.
  • Ran: typecheck, bun run test (all pass), bun run test:e2e -- status-test (96 pass).

🤖 Generated with Claude Code

expo-tuft Bot added a commit that referenced this pull request Sep 15, 2026
…list, and add the CHANGELOG entry for #70

Co-authored-by: kudo <46429+kudo@users.noreply.github.com>
@Kudo
Kudo added this pull request to stack #78 September 16, 2026 06:07
expo-tuft Bot added a commit that referenced this pull request Sep 16, 2026
…list, and add the CHANGELOG entry for #70

Co-authored-by: kudo <46429+kudo@users.noreply.github.com>
@expo-tuft
expo-tuft Bot force-pushed the kudo/status-2-ota-cache branch from 23b789c to c138c61 Compare September 16, 2026 19:25
expo-tuft Bot added a commit that referenced this pull request Sep 24, 2026
…list, and add the CHANGELOG entry for #70

Co-authored-by: kudo <46429+kudo@users.noreply.github.com>
@expo-tuft
expo-tuft Bot force-pushed the kudo/status-2-ota-cache branch from eb1317c to 5076ba9 Compare September 24, 2026 14:28
@expo-tuft
expo-tuft Bot force-pushed the kudo/status-2-ota-cache branch 2 times, most recently from bde1c76 to d77a632 Compare October 15, 2026 15:03
Comment thread packages/@expo/agent-cli/src/impact/runtimeVersion.ts Outdated
Comment thread packages/@expo/agent-cli/src/impact/runtimeVersion.ts
@expo-tuft
expo-tuft Bot force-pushed the kudo/status-2-ota-cache branch 2 times, most recently from 75b7e6a to 0cd6a51 Compare October 15, 2026 19:34
@Kudo
Kudo requested a review from vonovak October 15, 2026 20:20

@vonovak vonovak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as discussed on slack we might take another approach first but posting anyway :)

Comment thread packages/@expo/agent-cli/src/project/evaluatedAppConfig.ts Outdated
Comment thread packages/@expo/agent-cli/src/impact/types.ts Outdated
Comment thread packages/@expo/agent-cli/src/project/evaluatedAppConfig.ts Outdated
@expo-tuft
expo-tuft Bot force-pushed the kudo/status-2-ota-cache branch from 0cd6a51 to b796b20 Compare October 16, 2026 10:29
@Kudo
Kudo requested a review from vonovak October 16, 2026 10:35
expo-tuft Bot and others added 8 commits October 19, 2026 01:25
…ict, and remembers the evaluation of a dynamic one

The OTA verdict of `status --explain` spawned `expo config --json --type
public` on every run, to be told what `app.json` says. For a static config
that file *is* the public config, so the spawn — about a second — learned
nothing. It is now one file read (`readStaticExpoConfigAsync`).

A dynamic `app.config.js`/`.ts` still has to be evaluated, by the project's
own Expo CLI (llp/0001 §Constraints item 5). The answer is now remembered in
`.expo/agent-cli-app-config.json` (`src/project/evaluatedAppConfig.ts`),
revalidated against the same size-and-mtime manifest the fingerprint record
uses, under the same ten-minute expiry — the gaps are the same: a dynamic
config can read `process.env` or the date, and no stamp sees that. The report
says when the answer came out of the record (`runtimeVersion.cache`; `cached
2m ago` in the text), and `--no-fingerprint-cache` refuses it, the way it
refuses the fingerprint record and the remembered EAS answer.

`parseLastJsonObject` moves beside the subprocess it reads; `runtimeVersion.ts`
re-exports it for its callers.

Co-authored-by: kudo <46429+kudo@users.noreply.github.com>
…list, and add the CHANGELOG entry for #70

Co-authored-by: kudo <46429+kudo@users.noreply.github.com>
Co-authored-by: kudo <46429+kudo@users.noreply.github.com>
A plain package.json reaches the OTA verdict through status, and evaluating
it meant npx expo fetching a package to answer a question nothing asked —
a 300 s hang on the Windows tier0 shard. No app config is no runtimeVersion.

Co-authored-by: kudo <46429+kudo@users.noreply.github.com>
Co-authored-by: kudo <46429+kudo@users.noreply.github.com>
…n it parses, and pin Expo's file order

Co-authored-by: kudo <46429+kudo@users.noreply.github.com>
…ig does not check the environment, and drop the unused clear

Co-authored-by: kudo <46429+kudo@users.noreply.github.com>
…rop the app config cache

A dynamic config can read env vars and .env files, which the file stamps do not see.
A static app.json can list config plugins that set runtimeVersion.
@Kudo
Kudo force-pushed the kudo/status-2-ota-cache branch from b796b20 to f11b3dd Compare October 18, 2026 18:11
@Kudo Kudo changed the title [agent-cli] status reads a static app.json as a file for the OTA verdict, and remembers the evaluation of a dynamic one [agent-cli] The OTA verdict keeps expo config on every run, and skips it for a project with no app config Oct 18, 2026

@vonovak vonovak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one suggestion

if (!staticConfig.source || !staticConfig.config) {
return { policy: null, literal: null, source: null };
}
return readRuntimeVersion(staticConfig.config.runtimeVersion, staticConfig.source);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 suggestion] When expo config fails, this falls back to runtimeVersion from the static file.

Could a failed evaluation return source: null, so the verdict is unknown?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, done in 5eb90b0. A failed expo config now gives source: null, so the verdict is unknown. The static file is not a fallback any more, for the same reason we stopped reading it directly: its plugins or a dynamic config can change the runtimeVersion the app sees. The e2e for the failed subprocess on #71 now expects source: null (5c5340b).

…tead of reading the static file

Its plugins, or a dynamic config, can change the runtimeVersion the app sees.
@Kudo
Kudo requested a review from vonovak October 19, 2026 10:58
@Kudo
Kudo merged commit 044362f into main Oct 19, 2026
24 of 25 checks passed
@Kudo
Kudo deleted the kudo/status-2-ota-cache branch October 19, 2026 14:47
Kudo added a commit that referenced this pull request Oct 19, 2026
…t changed, the OTA verdict, and the EAS answer (#71)

Stack 3/4 of the plan to remove `status --explain`. Based on #70 (which
is based on #69).

## Why

`--explain` gated three things — the per-source change list, the OTA
verdict, the EAS build lookup — because two of them cost a second or
more. "That's not friendly to use." #69 and #70 made those costs
rememberable, so the flag has nothing left to gate.

## How

- `--explain` is removed with no alias (an unknown option, exit 1), the
way `--cloud` was in #34.
- Every run carries `changedSources`, `ota`, and the EAS lookup.
`--build <id>` no longer needs `--explain`.
- The two per-platform fingerprints of a linked project start at the top
of `collectStatusReportAsync`, beside the probe's own, and the lookup
joins them through the in-process memo (LLP 0023 Layer 1). A cold run of
a linked project costs the wall clock one `fingerprint:generate` instead
of two. An unlinked project starts none.
- `BuildsStatus.askedEas` now means "this run called EAS for at least
one platform" (false when every platform was answered from the record or
gated: signed out, not linked, no fingerprint). Same key, same type; the
`cli:status` event's `easBuildsAsked` follows.
- The `eas build` line prints whenever EAS has answered (`found` or
`none`) or was called; it stays out when every platform was gated,
because the freshness rows carry that reason once.
- The change list prints platforms with identical source lists once:
`changed ios, android: 3 sources`.
- `--no-fingerprint-cache` help text now says what it refuses:
fingerprints, the evaluated app config, the remembered EAS answer.
- Prose that named `status --explain`: help, `AGENTS.md` content,
workflow help, the `--assert` refusal, the `change-ota-unsafe`
follow-up, `.expo/README.md`, code comments. LLP 0004 §Status, 0011,
0016, 0022, 0023, 0027 updated.

## Test Plan

- Unit: `statusAsync-test` "what every run carries" (classification, OTA
+ EAS on every run, refused cache threaded to both, section isolation);
`easBuilds-test` without `lookUp` (asks both platforms, answers from the
record per platform, `askedEas` false when nothing was asked, asks again
over a stale key, remembers nothing without a project hash);
`resolveOptions-test` for `--build` alone; `format-test` /
`sections-test` strings.
- E2E `status-test.ts`: the former `--explain` cases run as plain runs;
`--explain` exits 1; `--build build-1` alone is taken and echoed; a
linked plain run asks EAS twice and prints `eas build … ios: none`; an
unlinked plain run prints no `eas build` line and names `eas init`; the
impact headline carries `changedSources`; three fingerprints on a linked
run, one on an unlinked one.
- `help-test`, `eas-runner-test`, `eas-shim-test`,
`e2e-live/live-eas-test` updated for the missing flag.
- Ran: `bun run typecheck`, `bun run lint`, `bun run test` (4228 pass),
`bun run test:e2e -- status-test eas-runner-test eas-shim-test
help-test` (all pass).

---------

Co-authored-by: expo-tuft[bot] <288127324+expo-tuft[bot]@users.noreply.github.com>
Co-authored-by: kudo <46429+kudo@users.noreply.github.com>
Co-authored-by: Kudo <kudo@expo.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants