Skip to content

[eas-cli] Show simulator session errors in simulator:get - #4694

Merged
brentvatne merged 12 commits into
mainfrom
@brent/simulator-get-errors
Oct 20, 2026
Merged

brentvatne merged 12 commits into
mainfrom
@brent/simulator-get-errors

Conversation

@brentvatne

@brentvatne brentvatne commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Why

  • eas simulator:get on a failed session shows Status ERRORED and nothing about why, in both human and --json output.
  • Failures without a specific error code reach the CLI only as UNKNOWN_ERROR / UNKNOWN_CUSTOM_BUILD_ERROR, "Unknown error. See logs for more information." Every failed adb or simctl call ends up there. The real cause is only in the job run log.

How

Shared lookup (src/simulator/jobRunErrors.ts, used by simulator:start and simulator:get):

  • Job run errors: fetched with the failure-only query from [eas-cli] Show why a simulator session failed to start #4693.
  • Unknown errors: when every job run error is an unknown error, the CLI downloads the job run log and prints:
    • for the first step that failed (marker: end-step, result: fail): the stderr line that names an error, exception or fatal error, the stderr line right after it, and the step's last error-level line;
    • for later failed steps, only their last error-level line;
    • at most four lines, each truncated to 500 characters.
    • If no step failed, it prints nothing from the log and keeps the job run's own message. That way an error a successful step ignored (for example "Failed to upload … Ignoring error.") is never shown as the cause.
  • Masked text only: it prints the log line's msg, which the worker masks, and never err.message or err.stack, which it doesn't.
    • Unknown failures inside a build phase log an empty msg (context.ts:310). For those, the CLI keeps the job run's own message, which already names the phase.
    • A separate worker PR masks err too.
  • Query bound: the same bounded, quiet, non-retried lookup as [eas-cli] Show why a simulator session failed to start #4693, plus logFileUrls.
  • Download bound:
    • it goes through eas-cli's proxy-aware fetch, which throws on status ≥ 400;
    • it has a 20 MiB limit and its own 10s timeout, and Ctrl+C cancels it too;
    • an owned AbortController is aborted in finally, so the connection closes even after a size-limit rejection.
  • Fallback: any failure falls back to the job run's own error messages. Debug output leaves out node-fetch's error message, which contains the signed log URL.

simulator:get: it looks up the errors only when the session or its job run is ERRORED. Human output gets a "Session errors:" section, and --json always includes errors: [{ errorCode, message, docsUrl? }].

Against a real Android session that failed with UNKNOWN_ERROR (a launch argument with a space):

Status       ERRORED
Session errors:
  Launch application: Error: Activity not started, unable to resolve Intent { act=android.intent.action.MAIN … pkg=launchArgProbe }
  Launch application: adb shell am start launchArgProbe hello world -n dev.brent.euxy/dev.brent.euxy.MainActivity exited with non-zero code: 1

Before, that printed "Unknown error. See logs for more information."

Test Plan

  • 251 tests pass across src/commands/simulator, jobRunErrors.test.ts, DeviceRunSessionQuery-test.ts and createGraphqlClient.
  • Log selection: tests use the shape of a real log, and cover:
    • a phase failure with a secret in err (nothing printed);
    • stderr from a step that didn't fail, and an error a successful step ignored (both left out);
    • a two-line stderr cause;
    • a cleanup step failing after the real failure (the first step's cause is kept).
  • Transport: tests run against a real loopback server through the real src/fetch.ts, with https_proxy cleared. They cover a 404, an oversized log, a stalled download, Ctrl+C, and an already-aborted signal, and check that the server sees the connection close.
  • Query cancellation: tested against a real urql client and a server that never answers.
  • Mutation checks: each of these fails a test: removing the finally abort; removing the unsubscribe on abort; restoring the whole-log fallback; logging the full download error.
  • yarn typecheck, oxlint and oxfmt --check pass.
  • Ran simulator:get from this branch against the production session above and an iOS session with a bad --device.

🤖 Generated with Claude Code

simulator:start only printed the session status when a session errored
before it was ready. The reason is already on the session's job run, so
fetch turtleJobRun.errors and print each message (and docs link) on its
own line.
…s for unknown errors

Fetch job run errors with a separate query when a session fails instead of
on every poll of the shared session query. When the job run only reports an
unknown error, print the failed step errors from its logs. Also show the
errors when a session fails after it is ready.
Move the job run error lookup and unknown-error log fallback into
simulator/jobRunErrors so simulator:start and simulator:get share it.
simulator:get fetches the errors only for errored sessions, prints them in
a Session errors section, and always includes an errors array in --json.
@github-actions

Copy link
Copy Markdown

Subscribed to pull request

File Patterns Mentions
packages/eas-cli/** @douglowder

Generated by CodeMention

Warning: The preamble and epilogue options in commentConfiguration are deprecated. Use template instead.

@codecov

codecov Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.37%. Comparing base (f775b10) to head (e20caba).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4694      +/-   ##
==========================================
+ Coverage   75.33%   75.37%   +0.05%     
==========================================
  Files        1053     1054       +1     
  Lines       52257    52338      +81     
  Branches    11383    11446      +63     
==========================================
+ Hits        39362    39445      +83     
+ Misses      11907    11905       -2     
  Partials      988      988              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Drop the job run log fallback from this change, which printed unmasked
err.message lines, read logs for stopped sessions and had no timeout or size
limit. The bounded, masked log fallback lands in the simulator:get follow-up.
…e and mask-safe

- Print only the masked log message of error-level lines, never err.message.
- Prefer the failed step's lines, with its last stderr line that names a cause.
- Read logs only when every job run error is an unknown error.
- Cancel the errors query on Ctrl+C or after 10s by unsubscribing from urql.
- Download through the proxy-aware fetch with a 20 MiB limit and its own 10s
  timeout, and always close the connection.
Give the lookup a 10s deadline and the session's Ctrl+C signal, and cancel
its request by unsubscribing from urql, so it can't hold up stopping the
session. Don't retry it or print the transient-error message, since it's
best-effort. Cap each error message at 500 characters.
…idn't fail

- Drop the whole-log fallback, which could show an error a successful step
  ignored instead of the job run's own message.
- List the first failed step first, with its stderr cause and the line after
  it; later failed steps only add their error line.
- Make the errors lookup quiet and not retried, like the base branch.
- Keep the signed log URL out of debug output, and cap job run messages.
@brentvatne
brentvatne requested a review from gwdp October 20, 2026 04:27

@gwdp gwdp 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, nice UX improvement!

Codex found one small P2 worth fixing while here (optional): strip terminal control characters from downloaded error messages and step names before printing them, so crafted log text cannot clear the terminal or spoof output.

— Codex

…rors

Job run error messages and log lines from the session are printed in the
terminal, so remove escape sequences and control characters before printing
them. Match the stderr cause and skip empty error lines on the stripped text,
so a colored "Error:" line is still found.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@brentvatne

Copy link
Copy Markdown
Member Author

Done in 669c0eb. Job run error messages, step names and log lines now have escape sequences and control characters stripped before they're printed, with newlines collapsed so a message can't fake extra lines. That also fixed a related bug: a colored Error: stderr line wasn't being picked as the cause, because the pattern was matched against the raw text.

Base automatically changed from @brent/simulator-start-error-detail to main October 20, 2026 20:51
…errors

# Conflicts:
#	packages/eas-cli/src/commands/simulator/__tests__/index.test.ts
#	packages/eas-cli/src/commands/simulator/index.ts
#	packages/eas-cli/src/graphql/generated.ts
#	packages/eas-cli/src/graphql/queries/DeviceRunSessionQuery.ts
#	packages/eas-cli/src/graphql/queries/__tests__/DeviceRunSessionQuery-test.ts
@github-actions

Copy link
Copy Markdown

✅ Thank you for adding a changeset!

@brentvatne
brentvatne merged commit 56646b7 into main Oct 20, 2026
7 checks passed
@brentvatne
brentvatne deleted the @brent/simulator-get-errors branch October 20, 2026 21:00
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