Skip to content

Conversation

@dfed
Copy link

@dfed dfed commented Oct 30, 2025

📜 Description

If an upload receives a 4xx error, Sentry should assume that a retry will not succeed and drop it on the floor. Currently Sentry will retry the upload (seemingly forever), which means that a 4xx error on upload will prevent future uploads from succeeding.

💡 Motivation and Context

This PR represents the shortest-path resolution to #6612. I'd prefer a solution where a 4xx error didn't cause a silent failure, or a solution where breadcrumb were automatically truncated. But this change is simple and unsticks existing clients, which seems like a good thing.

💚 How did you test it?

I didn't.

📝 Checklist

You have to check all boxes before merging:

  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

Comment on lines +417 to +418
if (response.statusCode >= 400 && response.statusCode < 500 && response.statusCode != 429) {
SENTRY_LOG_DEBUG(@"Received 4xx response code: %li", (long)response.statusCode);
Copy link
Author

Choose a reason for hiding this comment

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

happy to make this specific to 400 status codes if we don't want to go broader, but generally speaking 4xx is "client screwed up and shouldn't try again"

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I agree that 400 usually means the client shouldn't retry again, but I still have to double-check if they might not have a good reason to keep the envelope.

The somewhat good news is that the SDK will delete the faulty envelope once the cache is full. The cache has a default max size of 100 envelopes. So when you log more than 100 errors, the SDK will send out all the other errors. Of course this isn't ideal, but at least the other envelopes end up in Sentry eventually.

Copy link
Author

Choose a reason for hiding this comment

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

Sweet. Happy to update this PR. Let me know what you find!

Copy link
Member

Choose a reason for hiding this comment

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

Hey @dfed, sorry that it took so long. So yes, we must drop envelopes when getting 400 and also 500 status codes and we must also record a client report with the reason send_error. When we get a 429, we must also drop the envelope, but we must not record a client report, as the backend already does this, and otherwise we would be double-counting.

We record client reports here

- (void)recordLostEventFor:(NSArray<SentryEnvelopeItem *> *)items
{
for (SentryEnvelopeItem *item in items) {
NSString *itemType = item.header.type;
// We don't want to record a lost event when it's a client report.
// It's fine to drop it silently.
if ([itemType isEqualToString:SentryEnvelopeItemTypes.clientReport]) {
continue;
}
SentryDataCategory category = sentryDataCategoryForEnvelopItemType(itemType);
[self recordLostEvent:category reason:kSentryDiscardReasonNetworkError];
[self recordLostSpans:item reason:kSentryDiscardReasonNetworkError];
}
}

We don't have the send_error category yet, so we need to add it here

/**
* A reason that defines why events were lost, see
* https://develop.sentry.dev/sdk/client-reports/#envelope-item-payload.
*/
typedef NS_ENUM(NSUInteger, SentryDiscardReason) {
kSentryDiscardReasonBeforeSend = 0,
kSentryDiscardReasonEventProcessor = 1,
kSentryDiscardReasonSampleRate = 2,
kSentryDiscardReasonNetworkError = 3,
kSentryDiscardReasonQueueOverflow = 4,
kSentryDiscardReasonCacheOverflow = 5,
kSentryDiscardReasonRateLimitBackoff = 6,
kSentryDiscardReasonInsufficientData = 7
};
static DEPRECATED_MSG_ATTRIBUTE(
"Use nameForSentryDiscardReason() instead.") NSString *_Nonnull const SentryDiscardReasonNames[]
= { @"before_send", @"event_processor", @"sample_rate", @"network_error", @"queue_overflow",
@"cache_overflow", @"ratelimit_backoff", @"insufficient_data" };

More info on client reports is here: https://develop.sentry.dev/sdk/telemetry/client-reports/ and this PR updates the develop docs on this topic getsentry/sentry-docs#15571

@dfed, I leave it up to you. Feel free to implement the changes and also add tests for it, or we can take it from here and push the PR over the finish line.

Copy link
Author

Choose a reason for hiding this comment

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

Happy to let y'all take it over the line. Feel free to close this PR or push to it: whatever makes your lives easier.

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