Skip to content

[eas-cli] metadata: re-throw MetadataDownloadError / MetadataUploadError so exit code reflects failure#3692

Open
Maples7 wants to merge 1 commit into
expo:mainfrom
Maples7:fix/metadata-error-exit-code
Open

[eas-cli] metadata: re-throw MetadataDownloadError / MetadataUploadError so exit code reflects failure#3692
Maples7 wants to merge 1 commit into
expo:mainfrom
Maples7:fix/metadata-error-exit-code

Conversation

@Maples7
Copy link
Copy Markdown

@Maples7 Maples7 commented May 8, 2026

Why

eas metadata:push (and to a lesser extent eas metadata:pull) currently exits with status 0 even when individual entity sync operations fail. In production this means an apparently-successful run can leave App Store Connect in a partially-synced state with the user none the wiser.

Concrete repro that triggered this fix: a real metadata:push run printed multiple Failed uploading <screenshot>.png (<locale>) lines via the logAsync spinner (transient ASC 503s during peak hours) and still exited 0, so the surrounding bash retry loop saw "success" and stopped.

✖ Failed uploading 03.png (en-US)
✖ Failed uploading 04.png (en-US)
🎉 Store configuration is synced with the app stores.
$ echo $?
0

Closes #3689.

Root cause

logAsync correctly re-throws when the wrapped action rejects, and uploadMetadataAsync / downloadMetadataAsync correctly accumulate those errors and throw a wrapping MetadataUploadError / MetadataDownloadError. The exit code is dropped one layer up, in handleMetadataError:

if (error instanceof MetadataDownloadError || error instanceof MetadataUploadError) {
  Log.newLine();
  Log.error(chalk.bold(error.message));
  // ...print per-error messages and "open an issue" link...
  return; // <-- swallows the failure; oclif sees no error → exit 0
}

MetadataValidationError was swallowed the same way, even though the upstream call sites in commands/metadata/push.ts and commands/metadata/pull.ts already treat handleMetadataError as terminal (it's the last call in their catch block).

How

Replace both return; branches in handleMetadataError with throw error; so the error propagates up to EasCommand.catch, which logs it via Sentry and re-throws a sanitized ${commandId} command failed. error. oclif then exits non-zero. Validation errors are now also propagated (matches the existing JSDoc which already promises "this method will re-throw that error").

The friendly multi-line block (error.message + per-error messages + "If this issue persists, open a new issue at …") is still printed exactly as before, so the visible UX is unchanged for users who were already paying attention to the spinner output. The only user-visible change is that the process now exits 1, which CI / wrapper scripts can finally observe.

Test Plan

Added unit tests in packages/eas-cli/src/metadata/__tests__/errors.test.ts covering all four branches of handleMetadataError (generic, validation, upload-aggregate, download-aggregate). Each asserts the original error instance bubbles back out so the calling command exits non-zero.

$ yarn test src/metadata/__tests__/errors.test.ts
PASS src/metadata/__tests__/errors.test.ts
  handleMetadataError
    ✓ re-throws a generic error untouched
    ✓ re-throws a MetadataValidationError so the command exits with non-zero status
    ✓ re-throws a MetadataUploadError so the command exits with non-zero status
    ✓ re-throws a MetadataDownloadError so the command exits with non-zero status
Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total

Full metadata suite still passes (yarn test src/metadata → 16 suites, 191 tests, 187 baseline + 4 new). yarn typecheck clean. yarn lint clean (0 errors; the 12 pre-existing warnings are unchanged).

I haven't been able to E2E-reproduce a transient ASC 503 inside the change because I'd need to be able to deterministically force AppScreenshot.uploadAsync to throw — happy to add an integration-style test if there's a preferred mocking pattern in this repo.

/changelog-entry bug-fix [eas-cli] Fix eas metadata:push and eas metadata:pull exiting with status 0 even when individual screenshot, app-info, or other entity sync operations failed.

…dataUploadError so exit code reflects failure

handleMetadataError previously logged the aggregated errors and then returned,
swallowing the failure. The MetadataPush/MetadataPull commands therefore exited
with status 0 even when individual entity sync operations (screenshot upload,
delete, reorder, app-info, etc.) failed inside logAsync. Re-throwing the error
lets EasCommand.catch propagate it to oclif and the process exits non-zero.

Refs expo#3689
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

Subscribed to pull request

File Patterns Mentions
**/* @douglowder
packages/eas-cli/src/metadata/** @byCedric

Generated by CodeMention

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.

metadata:push: exit code does not reflect screenshot upload / delete / reorder failures

1 participant