Skip to content

fix(analytics-core): drop events with an empty event type before upload - #1920

Open
Mercy811 wants to merge 1 commit into
mainfrom
xinyiye/sdkw-50-browser-sdk-drop-events-with-an-empty-event-type-before
Open

fix(analytics-core): drop events with an empty event type before upload#1920
Mercy811 wants to merge 1 commit into
mainfrom
xinyiye/sdkw-50-browser-sdk-drop-events-with-an-empty-event-type-before

Conversation

@Mercy811

@Mercy811 Mercy811 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

SDKW-50 — 99.6% of dropped Browser SDK events have a blank event name and get 400d by the event server (Slack thread). Beyond being pointless traffic, a blank event type in a batch forces the rest of that batch to be retried.

This adds the same client-side validation the Kotlin SDK just picked up in amplitude/Amplitude-Kotlin#444: drop the event before upload, with a warning log.

Where: AmplitudeCore.process() (packages/analytics-core/src/core-client.ts), right after the existing optOut check — the direct analogue of Kotlin's Amplitude.process(). This is the single funnel into the timeline: both dispatch() and dispatchWithCallback() route through it, so the check covers track/logEvent, identify, groupIdentify, setGroup, and revenue for every SDK built on core (browser, node, react-native).

What counts as empty: event_type that is empty, whitespace-only, or not a string. Kotlin's isBlank() covers empty + whitespace; the typeof guard is the JS-specific part — track({}) or track(undefined) reaches process() with event_type === undefined, since createTrackEvent passes object input straight through.

Dropped events return buildResult(event, 0, EMPTY_EVENT_TYPE_MESSAGE), matching how optOut and CLIENT_NOT_INITIALIZED already report skipped events, so callers awaiting track() get a result rather than a hang, and the promise message matches the logged warning.

Not covered (deliberate, matches Kotlin): the destination plugin's setup() restores unsent events from storage and calls execute() directly, bypassing process(). Blank events persisted by older SDK versions will still be uploaded once — but a 400 lists them in eventsWithInvalidFields, so handleInvalidResponse drops them instead of retrying forever. Not worth a second gate.

Testing

New tests in packages/analytics-core/test/core-client.test.ts under describe('process'), mirroring the Kotlin test's shape (dropped event never reaches the pipeline; a valid event still flows):

  • test.each over empty / whitespace-only / missing event_type — asserts timeline.push is never called, the result is EMPTY_EVENT_TYPE_MESSAGE with code 0, and exactly one warning is logged.
  • A valid event_type still reaches timeline.push with no warning.

Verified locally:

  • analytics-core: 873 tests pass, 100% statements/branches/functions/lines (the package's enforced threshold).
  • analytics-browser: 499 tests pass, 100% coverage.
  • Full workspace pnpm test: all 29 projects pass.
  • pnpm lint (0 errors) and pnpm typecheck clean on analytics-core.

Checklist

  • Does your PR title have the correct title format?
  • Does your PR have a breaking change?: No — but note it is a behavior change: an event that previously made a doomed round-trip to the server is now dropped locally and its track() promise resolves with code 0 instead of 400.

🤖 Generated with Claude Code


Note

Low Risk
Narrow validation in the shared event funnel; no API or auth changes, though callers now get a local code-0 skip instead of a failed upload for blank event names.

Overview
Client-side guard in AmplitudeCore.process() drops events whose event_type is missing, not a string, or only whitespace—right after the existing optOut check and before timeline.push. That blocks doomed uploads that 400 the event server and can force batch retries.

Dropped events follow the same skip pattern as opt-out: buildResult with code 0, message EMPTY_EVENT_TYPE_MESSAGE, and a warning on the logger. Valid event types still go through the pipeline unchanged.

Tests cover empty, whitespace-only, and undefined event_type (no push, correct result/warn) plus a happy path that still calls push.

Reviewed by Cursor Bugbot for commit a72764b. Configure here.

Events with a blank event type are rejected by the event server with a 400,
which also forces the rest of their upload batch to be retried. 99.6% of
dropped Browser SDK events have a blank event name.

Validate client-side in AmplitudeCore.process() instead: events whose
event_type is empty, whitespace-only, or not a string are dropped with a
warning log before reaching the timeline or any destination.

Mirrors the same check added to the Kotlin SDK in
amplitude/Amplitude-Kotlin#444.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Mercy811
Mercy811 requested a review from a team as a code owner July 31, 2026 21:36
@linear-code

linear-code Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

SDKW-50

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a72764b815

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/analytics-core/src/core-client.ts
@Mercy811

Copy link
Copy Markdown
Contributor Author

bugbot run

@github-actions

Copy link
Copy Markdown

size-limit report 📦

Path Size
packages/analytics-browser/lib/scripts/amplitude-min.js.gz 61.51 KB (+0.09% 🔺)
packages/session-replay-browser/lib/scripts/session-replay-browser-min.js.gz 134.97 KB (0%)
packages/unified/lib/scripts/amplitude-min.umd.js.gz 215.59 KB (+0.03% 🔺)
@amplitude/element-selector (gzipped esm) 2.67 KB (0%)

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit a72764b. Configure here.

// upload batch to be retried, so drop it before it ever reaches a destination.
const eventType: unknown = event.event_type;
if (typeof eventType !== 'string' || eventType.trim().length === 0) {
this.config.loggerProvider.warn(EMPTY_EVENT_TYPE_MESSAGE);

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.

Should this be a warning or an error? I feel like this justifies an "error" because it's something customers will want surfaced quickly so they can take corrective action right away.

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