Skip to content

fix(artifacts): Report distribution errors for unsupported and skipped builds (EME-422)#567

Merged
runningcode merged 11 commits intomainfrom
no/eme-422-update-artifact-error-unknown-type
Apr 15, 2026
Merged

fix(artifacts): Report distribution errors for unsupported and skipped builds (EME-422)#567
runningcode merged 11 commits intomainfrom
no/eme-422-update-artifact-error-unknown-type

Conversation

@runningcode
Copy link
Copy Markdown
Contributor

@runningcode runningcode commented Feb 23, 2026

Summary

When Sentry requests BUILD_DISTRIBUTION, Launchpad now reports back via
the distribution endpoint in all cases where it can't fulfill the request:

  • Unknown artifact types: Reports PROCESSING_ERROR via update_distribution_error
  • Invalid code signature (iOS): Reports SKIPPED with reason invalid_signature
  • Simulator builds (iOS): Reports SKIPPED with reason simulator

Previously, these cases either silently did nothing or only logged an error,
leaving the artifact stuck in a "processing" state in Sentry with no timeout
to recover.

Fixes EME-422

🤖 Generated with Claude Code

@linear
Copy link
Copy Markdown

linear Bot commented Feb 23, 2026

@runningcode runningcode requested a review from chromy February 23, 2026 09:34
@runningcode runningcode marked this pull request as ready for review February 23, 2026 09:34
Comment thread src/launchpad/artifact_processor.py
Comment thread src/launchpad/artifact_processor.py
Comment thread src/launchpad/artifact_processor.py
runningcode added a commit to getsentry/sentry that referenced this pull request Mar 5, 2026
Add a dedicated endpoint (`PUT
.../files/preprodartifacts/{id}/distribution/`) for launchpad to report
distribution processing errors back to the monolith. This mirrors the
existing `ProjectPreprodSizeEndpoint` pattern.

When launchpad encounters a distribution failure (unsupported artifact
type, invalid code signature, simulator build), it needs a way to set
`installable_app_error_code` and `installable_app_error_message` on the
artifact so the frontend can display the reason. Previously, the only
option was the general `update` endpoint which marks the entire artifact
as failed — but distribution errors shouldn't affect the artifact's
overall state.

Follow-up to #109062. The launchpad side that calls this endpoint is in
getsentry/launchpad#567.

Refs EME-842, EME-422

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
JonasBa pushed a commit to getsentry/sentry that referenced this pull request Mar 5, 2026
Add a dedicated endpoint (`PUT
.../files/preprodartifacts/{id}/distribution/`) for launchpad to report
distribution processing errors back to the monolith. This mirrors the
existing `ProjectPreprodSizeEndpoint` pattern.

When launchpad encounters a distribution failure (unsupported artifact
type, invalid code signature, simulator build), it needs a way to set
`installable_app_error_code` and `installable_app_error_message` on the
artifact so the frontend can display the reason. Previously, the only
option was the general `update` endpoint which marks the entire artifact
as failed — but distribution errors shouldn't affect the artifact's
overall state.

Follow-up to #109062. The launchpad side that calls this endpoint is in
getsentry/launchpad#567.

Refs EME-842, EME-422

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
@runningcode runningcode force-pushed the no/eme-422-update-artifact-error-unknown-type branch from 5eaf5cd to fe8817e Compare March 6, 2026 09:27
Comment thread src/launchpad/artifact_processor.py Outdated
@sentry
Copy link
Copy Markdown
Contributor

sentry Bot commented Mar 6, 2026

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
Hacker News com.emergetools.hackernews 1.0.2 (13) Release

⚙️ launchpad-test-android Build Distribution Settings

Comment thread src/launchpad/artifact_processor.py Outdated
@sentry
Copy link
Copy Markdown
Contributor

sentry Bot commented Mar 9, 2026

Sentry Build Distribution

App Version Configuration
Hacker News 1.0.2 (13) Release

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Unused project_id parameter creates monitoring blind spot
    • Added StatsD metrics emission to _update_distribution_skip following the pattern of sibling methods to track distribution skip events with project_id, organization_id, and skip_reason tags.

Create PR

Or push these changes by commenting:

@cursor push cbb5a57a4d
Preview (cbb5a57a4d)
diff --git a/src/launchpad/artifact_processor.py b/src/launchpad/artifact_processor.py
--- a/src/launchpad/artifact_processor.py
+++ b/src/launchpad/artifact_processor.py
@@ -433,6 +433,15 @@
         skip_reason: str,
     ) -> None:
         """Report distribution skip via the dedicated distribution endpoint."""
+        self._statsd.increment(
+            "artifact.distribution.skipped",
+            tags=[
+                f"skip_reason:{skip_reason}",
+                f"project_id:{project_id}",
+                f"organization_id:{organization_id}",
+            ],
+        )
+
         try:
             self._sentry_client.update_distribution_error(
                 org=organization_id,
This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment thread src/launchpad/artifact_processor.py Outdated
@runningcode runningcode force-pushed the no/eme-422-update-artifact-error-unknown-type branch from 5911136 to 10c7af7 Compare March 11, 2026 16:47
runningcode and others added 8 commits April 15, 2026 13:30
…on (EME-422)

Replace the TODO in _do_distribution() with a call to
_update_artifact_error() so that unsupported artifact types are properly
reported back to Sentry instead of silently failing.
When a ZippedXCArchive has an invalid code signature or is a simulator
build, _do_distribution now reports the specific error via
_update_artifact_error instead of silently doing nothing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The update_artifact endpoint silently ignored distribution_state and
distribution_skip_reason fields. Use the dedicated distribution endpoint
that accepts error_code and error_message instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…EME-422)

Unsupported artifact types are genuine errors, not intentional skips.
Report them with PROCESSING_ERROR instead of SKIPPED so they surface
correctly to users.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ns (EME-422)

Catch all exceptions (not just SentryClientError) when reporting
distribution errors/skips. Network errors like ConnectionError and
Timeout from requests aren't subclasses of SentryClientError, so they
would propagate uncaught and crash the pipeline. These are best-effort
notifications that should never block artifact processing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Revert the iOS-specific changes that reported skip reasons for invalid
code signatures and simulator builds. Keep the unsupported artifact
type error reporting via the distribution endpoint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When Sentry requests BUILD_DISTRIBUTION for an iOS artifact that has an
invalid code signature or is a simulator build, Launchpad was silently
doing nothing. This left the artifact stuck in a "processing" state
in Sentry with no timeout to recover. Report SKIPPED via the
distribution endpoint so Sentry can resolve the state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@runningcode runningcode force-pushed the no/eme-422-update-artifact-error-unknown-type branch from 10c7af7 to ec4a615 Compare April 15, 2026 12:45
@runningcode runningcode changed the title fix(artifacts): Report error for unknown artifact types in distribution fix(artifacts): Report distribution errors for unsupported and skipped builds (EME-422) Apr 15, 2026
Comment thread src/launchpad/artifact_processor.py Outdated
error_code=InstallableAppErrorCode.SKIPPED.value,
error_message=skip_reason,
)
except Exception:
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.

Maybe this should more closely mirror _update_artifact_error above - that way it could cover both the skip and the error cases - and report the skipped / distro errors to _statsd

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.

good point, added statsd and made the method look more similar to the above

Comment thread src/launchpad/sentry_client.py Outdated
Comment thread src/launchpad/sentry_client.py Outdated
)

if response.status_code != 200:
raise SentryClientError(response=response)
Copy link
Copy Markdown
Contributor

@chromy chromy Apr 15, 2026

Choose a reason for hiding this comment

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

Can this not use _make_json_request?

Comment thread src/launchpad/sentry_client.py Outdated
endpoint = f"/api/0/internal/{org}/{project}/files/preprodartifacts/{artifact_id}/update/"
return self._make_json_request("PUT", endpoint, UpdateResponse, data=data)

def update_distribution_error(self, org: str, artifact_id: str, error_code: int, error_message: str) -> None:
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.

I would be tempted to make this like update_artifact above (both naming and implementation wise)

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.

Good idea, updated. I had to change the _update_resonse_model method so that we don't return a ResponseModel since this is fire and forget.

Replace _report_distribution_skip with _update_distribution_error that
mirrors _update_artifact_error — covers both skip and error cases with
statsd metrics, structured logging, and SentryClientError handling.

Also refactor SentryClient: rename update_distribution_error to
update_distribution with a generic data dict, and make _make_json_request
model param optional so it can handle fire-and-forget PUT requests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment on lines +482 to +483
except SentryClientError:
logger.exception(f"Failed to update distribution error for artifact {artifact_id}")
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.

Bug: Network errors (ConnectionError, Timeout) from session.request() are not caught, causing them to propagate and be misreported as hard processing failures.
Severity: MEDIUM

Suggested Fix

Widen the exception handler in _update_distribution_error to catch all exceptions: change except SentryClientError: to except Exception:. This matches the PR's stated design intent and the comment in commit 3aa27629 which explicitly addressed this issue.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/launchpad/artifact_processor.py#L482-L483

Potential issue: In `_update_distribution_error`, only `SentryClientError` is caught
(line 482), but `session.request()` in `_make_json_request` can directly raise
network-level exceptions like `ConnectionError` or `Timeout` that are NOT subclasses of
`SentryClientError`. These propagate through `_update_distribution_error` →
`_do_distribution` → `process_artifact` → `process_message`, where the outer `except
Exception` logs them as "Processing failed" and increments `artifact.processing.failed`.
The PR's stated intent (as noted in commit `3aa27629`) is that these notifications are
"best-effort" and "should never block artifact processing", but the current
implementation violates this intent. A network transient error during skip reporting
would be misattributed as a hard failure.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown
Contributor

@chromy chromy left a comment

Choose a reason for hiding this comment

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

Lgtm

…l (EME-422)

Keep _make_json_request signature strict by introducing an EmptyResponse
model for endpoints that don't return meaningful data, rather than making
the model parameter optional.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread src/launchpad/sentry_client.py Outdated
Comment on lines +259 to +262
def update_distribution(self, org: str, artifact_id: str, data: Dict[str, Any]) -> EmptyResponse:
"""Update preprod artifact distribution."""
endpoint = f"/api/0/organizations/{org}/preprodartifacts/{artifact_id}/distribution/"
return self._make_json_request("PUT", endpoint, EmptyResponse, data=data)
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.

Bug: _make_json_request only handles HTTP 200, but the distribution endpoint may return 204 No Content, causing SentryClientError on success.
Severity: HIGH

Suggested Fix

Either: (1) update _make_json_request to accept both 200 and 204 success codes (returning an EmptyResponse() for 204), or (2) confirm the Sentry distribution endpoint always returns 200 with a JSON body. If it returns 204, handle it before calling model.model_validate(j).

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/launchpad/sentry_client.py#L259-L262

Potential issue: The `_make_json_request` method (used by `update_distribution`) only
treats HTTP 200 as success — any other status code triggers `raise
SentryClientError(response=response)`. The `EmptyResponse` model was introduced
specifically because the distribution endpoint doesn't return meaningful data,
suggesting its response body may be empty. A REST PUT endpoint that doesn't return a
body conventionally returns `204 No Content`. If Sentry's
`/api/0/organizations/{org}/preprodartifacts/{artifact_id}/distribution/` returns 204,
`_make_json_request` raises `SentryClientError`, which is caught-and-logged in
`_update_distribution_error`, meaning the skip/error notification silently fails every
time — defeating the purpose of this fix. Even if a 200 is returned but with an empty
body, `response.json()` would raise `JSONDecodeError`, also resulting in
`SentryClientError`.

…sponse (EME-422)

Replace EmptyResponse with DistributionResponse that models the actual
backend response (artifactId). Avoids misleading naming that could lead
to silent failures if the endpoint response changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@chromy chromy left a comment

Choose a reason for hiding this comment

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

lgtm

@runningcode runningcode merged commit 3b6990d into main Apr 15, 2026
22 checks passed
@runningcode runningcode deleted the no/eme-422-update-artifact-error-unknown-type branch April 15, 2026 15:39
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