Skip to content

[agent-cli] one reading of a failed eas run, starting with the EAS session start and stop - #126

Merged
vonovak merged 4 commits into
mainfrom
vonovak/eas-start-quotes-stderr-tail
Oct 12, 2026
Merged

vonovak merged 4 commits into
mainfrom
vonovak/eas-start-quotes-stderr-tail

Conversation

@vonovak

@vonovak vonovak commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

What was wrong

An EAS Simulator session start through bunx eas-cli@latest exited 1, and the report quoted Resolving dependencies as what EAS said [observed live, 2026-10-05]. On a cold scratch directory, bunx writes its install progress to stderr before the EAS CLI writes there. So the first line of stderr is the runner's line. This breaks llp/0021 rule 11 ("The runner is not the service").

Each caller that quotes a failed eas run reads its output in a different way. Some read the first stderr line, one reads stdout first, and one quotes the tail. The first-line looksLikeRunnerNoise guard has the same problem: when EAS writes after the runner, the guard says that the runner printed everything.

What changes

readEasFailure in src/utils/easFailure.ts reads a failed eas run in one way:

  1. A recognised EAS sentence (classifyEasFailure), with its fix.
  2. Else the first stderr line that is not the runner's and is not the CLI's closing Error: <command> command failed. line.
  3. Else the first stdout line.
  4. Else, if only the runner printed, "the runner did not deliver the CLI" (runnerNoiseReason).

easFailureReason writes that as one reason line. In this PR, the session start and the session stop use it. #127, stacked on this PR, moves the other callers and deletes looksLikeRunnerNoise.

The order uses the two observations. The session start error was on stderr. The build:list explanation for an unlinked project was on stdout, with only the generic closing line on stderr.

The second commit covers npx. On an empty npm cache, npx --yes eas-cli@latest writes npm warn deprecated … lines before the EAS CLI runs and npm notice … lines after it [observed — npm 10.9.4, 2026-10-07]. The runner list matched only npm warn exec, so a deprecation warning was quoted as what EAS said. The list now matches every npm warn and npm notice line. npm error stays out, because that is the runner failing.

This PR replaces the error-output part of #112. #112 quoted the last 20 lines of stderr, and llp/0021 rule 14 ("A message is not a tail") prohibits that.

Tests

  • src/utils/__tests__/easFailure-test.ts: each order rule, a recognised sentence after the runner, and the reason text.
  • src/dev/__tests__/openAppEas-test.ts: runner then error, runner only, and runner then a created session. The first and third cases fail on the earlier head of this PR.
  • Local: typecheck, lint, bun run format:check, bun run test.

🤖 Generated with Claude Code

@vonovak
vonovak force-pushed the vonovak/eas-start-quotes-stderr-tail branch from a832502 to b986f33 Compare October 7, 2026 13:15
@vonovak vonovak changed the title [agent-cli] a failed EAS session start quotes the end of its stderr, not the runner's first line [agent-cli] a failed EAS session start that only got runner progress says the runner did not deliver the CLI Oct 7, 2026
@vonovak
vonovak force-pushed the vonovak/eas-start-quotes-stderr-tail branch from b986f33 to 68f889e Compare October 7, 2026 14:02
…ssion start and stop (llp/0021 rules 11 and 14)

On a cold scratch directory bunx writes its install progress to stderr
before the EAS CLI writes there. A session start that failed this way
reported `Resolving dependencies` as what EAS said.

`readEasFailure` in src/utils/easFailure.ts reads a failed eas run once:
a recognised EAS sentence first, then the first line that is not the
runner's, and "the runner did not deliver the CLI" only when the runner
printed everything. `easFailureReason` puts that in one reason line.
The session start and stop use it. The other callers move to it in the
next PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vonovak
vonovak force-pushed the vonovak/eas-start-quotes-stderr-tail branch from 68f889e to 16f6e39 Compare October 7, 2026 14:04
@vonovak
vonovak added this pull request to stack #128 October 7, 2026 14:14
@vonovak vonovak changed the title [agent-cli] a failed EAS session start that only got runner progress says the runner did not deliver the CLI [agent-cli] one reading of a failed eas run, starting with the EAS session start and stop Oct 7, 2026
…1 rule 11)

`npx --yes eas-cli@latest` on an empty npm cache writes `npm warn
deprecated …` lines before the EAS CLI runs and `npm notice …` lines
after it [observed — npm 10.9.4, 2026-10-07]. The runner list matched
only `npm warn exec`, so a failed run quoted a deprecation warning as
what EAS said. The list now matches every `npm warn` and `npm notice`
line. `npm error` stays out: that is the runner failing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vonovak
vonovak requested review from Kudo and davidmokos October 7, 2026 14:49
}
const line =
firstLine(withoutRunnerNoise(stderr), EAS_COMMAND_FAILED) ??
firstLine(stdout) ??

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.

[bug] A failed simulator start used to quote the first stderr line. With no runner noise that line was Error: simulator command failed. It now skips that closing line and quotes the first stdout line. spawnCaptureAsync uses stdio: ['ignore', ...], so the child stdin is not a TTY. eas-cli's ora() then disables the spinner and writes it to stdout (packages/eas-cli/src/ora.ts, ora 5.1.0). start() prints - 🚀 Creating simulator session before succeed() prints Simulator session created, and the timeout sentence is a later stdout line (Log.error in EasCommand.catch, after pollSpinner.fail). The reason becomes exited 1: - 🚀 Creating simulator session. The timeout is dropped. Stop does the same with - 🛑 Stopping simulator session …. The new test puts the timeout on stderr, so it stays green. A captured run does not.

Suggestion: Skip the disabled-spinner lines (they start with - ) and the Simulator session created line, on stdout as well as stderr. The next line is the failure (✖ Timed out…, then the Timed out after … sentence). Add a fixture with that stdout/stderr split.

| { kind: 'nothing' };

export function readEasFailure({ stdout, stderr }: { stdout: string; stderr: string }): EasSaid {
const cause = classifyEasFailure(`${stdout}\n${stderr}`);

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.

[bug] Session start and stop now classify the whole capture before they pick a line. A GraphQL UNAUTHORIZED_ERROR is not "signed out". EasCommand.catch logs You don't have the required permissions… Run eas whoami… Run eas login to change the account. That text contains eas login, so the eas-login signature matches and the reason says this machine is not signed in. Before, the same failure quoted stderr's Error: GraphQL request failed. and did not claim the account was missing. The real signed-out sentence (log in with "eas login" plus EXPO_TOKEN) still needs to match.

Suggestion: Do not treat every eas login as signed out. Keep the signed-out wording (not logged in, must be logged in, or eas login next to EXPO_TOKEN). Leave the permissions sentence to be quoted as a line.

* `Error: build:list command failed.` names no cause. The EAS CLI prints it on stderr after it put the
* explanation on stdout [observed — live against an unlinked project, 2026-08-26].
*/
const EAS_COMMAND_FAILED = /^Error: \S+ command failed\.?$/;

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.

[bug] The skip only matches Error: <word> command failed. EasCommand.catch uses a second closing line for every GraphQL failure: it console.logs the explanation, then throws GraphQL request failed., which oclif prints as Error: GraphQL request failed. The unlinked build:list case works because its closing line matches the regex. A simulator failure in getContextAsync happens before any spinner, so stderr is only that GraphQL line. The reader quotes Error: GraphQL request failed. and drops the stdout sentence. That is the same bug the skip was added to fix.

Suggestion: Skip Error: GraphQL request failed. the same way, then read stdout. Pin it with a captured split, explanation on stdout and that one line on stderr.

const said = readEasFailure(result);
if (said.kind === 'runner-only') {
return runnerNoiseReason(
{ tool: 'eas', exitCode: result.exitCode },

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.

[suggestion] The other branch says exited on a signal when exitCode is null. The runner-only branch passes result.exitCode through, so a killed bunx (session deadline, stderr still Resolving dependencies) says it exited null. null !== 0, so session start and stop both reach this.

Suggestion: Use the same on a signal wording when there is no exit code.

@davidmokos davidmokos 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.

LGTM

@vonovak
vonovak merged commit e278289 into main Oct 12, 2026
15 checks passed
@vonovak
vonovak deleted the vonovak/eas-start-quotes-stderr-tail branch October 12, 2026 19:01
vonovak added a commit that referenced this pull request Oct 12, 2026
…eadEasFailure (#127)

Stacked on #126.

## What was wrong

`looksLikeRunnerNoise` read only the first stderr line. On a cold
scratch directory, bunx prints its progress first and the EAS CLI writes
its error after it. The guard then said that the runner printed
everything, and the EAS line was lost. Two independent reviews of #126
found this.

The other callers had their own versions of the same reading:

- The build lookup of `status --explain` (`src/impact/buildCache.ts`)
used the guard.
- The fingerprint compare (`src/impact/compare.ts`) used the guard, and
otherwise quoted the tail of stderr (rule 14).
- The session listing (`src/device/cloudSimulator.ts`) quoted the first
stderr line.
- The cloud reload (`src/runtime/reload/cloudReload.ts`) quoted the
first stderr line.

## What changes

All four use `readEasFailure` from #126. `looksLikeRunnerNoise` and
`runnerNoiseLine` are deleted. Their test cases move to the
`readEasFailure` tests. `runnerNoiseReason` now says "exited on a
signal" when there is no exit code.

The reload and the listing also get the recognised-sentence answers
(`classifyEasFailure`), the same as the build lookup.

## Tests

- `src/impact/__tests__/buildCache-test.ts` "quotes the CLI's line after
the runner's progress". It fails on #126 alone.
- Local: typecheck, lint, `bun run test`, and the e2e files
`cloud-simulator-test dev-eas-test eas-preflight-test eas-runner-test
eas-shim-test runtime-reload-test status-installed-test status-test`
(209 passed).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

3 participants