Skip to content

[agent-cli] every caller that quotes a failed eas run reads it with readEasFailure - #127

Merged
vonovak merged 3 commits into
vonovak/eas-start-quotes-stderr-tailfrom
vonovak/eas-failure-reason-callers
Oct 12, 2026
Merged

vonovak merged 3 commits into
vonovak/eas-start-quotes-stderr-tailfrom
vonovak/eas-failure-reason-callers

Conversation

@vonovak

@vonovak vonovak commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

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

🤖 Generated with Claude Code

@vonovak
vonovak added this pull request to stack #128 October 7, 2026 14:14
@vonovak vonovak changed the title [agent-cli] every caller that quotes a failed eas run reads it with readEasFailure (llp/0021 rules 11 and 14) [agent-cli] every caller that quotes a failed eas run reads it with readEasFailure Oct 7, 2026
@vonovak
vonovak requested review from Kudo and davidmokos October 7, 2026 14:39
@vonovak
vonovak force-pushed the vonovak/eas-failure-reason-callers branch from 3500b6f to 0f996dc Compare October 7, 2026 14:50

function firstLine(text: string): string {
return text.trim().split('\n')[0] ?? '';
return `${what}: ${easFailureReason(result, result.command)}`;

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] refusalReason now sends every failure that is not a controller error through easFailureReason. When stderr is only runner lines, that helper says the eas CLI never started and the fix is npm install --save-dev eas-cli. This command is simulator:exec npx agent-device@latest. stdin is not a TTY, so npm logs npm warn exec The following package was not found and will be installed: agent-device@latest and then installs. If the process stops there (the verb deadline sends SIGKILL, and spawnCapture keeps that as exitCode: null), those lines are all stderr has. Before, the reason quoted that line after exited on a signal. Now it says the eas CLI was not delivered. eas had already started. The package still being fetched is agent-device, and pinning eas-cli does not install it.

Suggestion: Keep readEasFailure for a recognised cause and for a real CLI line. On runner-only, if the quoted line names a package other than eas-cli, say that package was still installing. Keep the "eas CLI was not delivered" sentence for a line that is the outer eas-cli install. A unit test with only the agent-device npm warn exec line should not tell the reader to pin eas-cli.

`Why: "${[easCliLabel(easCli), ...args].join(' ')}" ${describeExit(result.exitCode, result.spawnError)}${
quoted ? `: ${quoted}` : ''
}`,
`How: check the id with "${easCommandPrefix()} build:list --limit 5 --json --non-interactive", and that this machine is signed in to the account that owns it.`,

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] A recognised failure now puts cause.summary on the Why line. The How line under it is still the old advice: check the build id, and check that this machine is signed in. Before, an unlinked project never reached that summary. The tail of stderr was quoted, and this How was the only next step. Now the Why says the project is not linked and names eas init, and the How still says to check sign-in. That is the wrong next step the summary exists to replace. build:list on an unlinked project fails the same way, so the How sends the reader back into the same error. A signed-out machine is fine, because the summary and this How agree.

Suggestion: When said.kind === 'cause', use said.cause.how as the How line. cloudSessionUnknownError already does that. Leave the build-id How for an unrecognised line.

`Why: "${[easCliLabel(easCli), ...args].join(' ')}" ${describeExit(result.exitCode, result.spawnError)}.`,
runnerCrashDetail({ tool: 'eas', exitCode: result.exitCode }, easCliLabel(easCli)),
`Why: ${runnerNoiseReason({ tool: 'eas', exitCode: result.exitCode }, easCliLabel(easCli), said.runnerLine)}.`,
`How: run this command again — the install completes once and the next run is warm. "npm install --save-dev eas-cli" pins the CLI into the project, which removes the download from every run.`,

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.

we should not recommend to install eas-cli locally. could you help to fix the problem as well?

@vonovak
vonovak force-pushed the vonovak/eas-failure-reason-callers branch from 0f996dc to 8a6e80d Compare October 8, 2026 12:50
vonovak and others added 2 commits October 8, 2026 15:02
…eadEasFailure (llp/0021 rules 11 and 14)

The build lookup and the fingerprint compare used looksLikeRunnerNoise,
which read only the first stderr line. On a cold scratch directory bunx
prints first and the EAS CLI writes its error after it, so the guard
said the runner did all of it and dropped the EAS line. The session
listing and the cloud reload quoted the first stderr line, which is the
runner's progress in the same case. The compare quoted the stderr tail.

All four now use readEasFailure. looksLikeRunnerNoise and
runnerNoiseLine are deleted, and their cases move to the
readEasFailure tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vonovak
vonovak force-pushed the vonovak/eas-failure-reason-callers branch from 8a6e80d to beebc25 Compare October 8, 2026 13:06

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

left one comment inline. this can make status --explain show a dependency warning instead of the error, so let's fix it before merging.

if (line) {
return line;
}
const said = readEasFailure(result);

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.

this can hide the actual error when eas-cli is installed in the project. EAS prints Found eas-cli in your project dependencies. to stderr, and we now pick that over the real error on stdout.

can we filter out these notices first? otherwise status --explain just shows the dependency warning instead of why the lookup failed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 0e25ce7.

readEasFailure now skips the eas-cli dependency notice: Found eas-cli in your project/monorepo dependencies., the cli.version line, and the Learn more link. The lookup's real error is quoted instead. The notice is quoted only when EAS printed nothing else, as evidence that EAS started. It also strips ANSI codes first, because FORCE_COLOR in our environment reaches eas-cli and colors the notice.

Tests: buildCache-test.ts has your case (the notice on stderr, the error on stdout), and easFailure-test.ts has the colored notice captured from eas-cli 24.12.1 with FORCE_COLOR=1. Both fail without the fix.

On the eas-cli side, expo/eas-cli#4591 stops printing the notice when stderr is not a terminal. We still need this filter for projects that pin an older eas-cli.

…1 rule 11)

eas-cli writes "Found eas-cli in your project dependencies." and two
more lines to stderr before a command that reads the project, when the
project depends on eas-cli. readEasFailure read that notice as the
failure, so status --explain showed it instead of the lookup's error on
stdout.

The notice now yields to every other line. It is quoted only when EAS
printed nothing else, as evidence that EAS started.

readEasFailure also strips ANSI codes first: FORCE_COLOR in the
caller's environment reaches eas-cli, and the colored notice did not
match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vonovak
vonovak requested review from Kudo and davidmokos October 9, 2026 20:46
@vonovak
vonovak merged commit d7c6f38 into main Oct 12, 2026
15 checks passed
@vonovak
vonovak deleted the vonovak/eas-failure-reason-callers branch October 12, 2026 19:01
vonovak added a commit that referenced this pull request Oct 12, 2026
…ssion start and stop (#126)

## 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](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