fix(analytics-browser): guard snippet bootstrap against undefined _iq and _q#1763
fix(analytics-browser): guard snippet bootstrap against undefined _iq and _q#1763Mercy811 wants to merge 2 commits into
Conversation
… and _q When window.amplitude (or window.amplitudeGTM) is pre-existing with invoked=true but missing _iq/_q, Object.keys(undefined) and queue.length both throw uncaught TypeErrors during SDK init. Together these account for ~70% of uncaught SDK errors in the planning section of the diagnostics dashboard. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc11e22e7b
ℹ️ 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".
There was a problem hiding this comment.
Pull request overview
This PR hardens the @amplitude/analytics-browser snippet and GTM-snippet bootstrap paths against cases where a pre-existing global amplitude/amplitudeGTM object is marked invoked=true but is missing the _iq and/or _q queues, preventing uncaught TypeErrors during initialization.
Changes:
- Guard
Object.keys(...)calls in snippet bootstrap to safely handle missing_iq. - Make
convertProxyObjectToRealObjectno-op when itsqueueargument isundefined, and add a regression test for that case. - Apply the same
_iqguard to the GTM snippet bootstrap.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
packages/analytics-browser/src/snippet-index.ts |
Avoids Object.keys(undefined) when _iq is missing during snippet bootstrap. |
packages/analytics-browser/src/gtm-snippet-index.ts |
Mirrors the snippet bootstrap guard for GTM (amplitudeGTM). |
packages/analytics-browser/src/utils/snippet-helper.ts |
Adds a runtime early-return when the queued-call array is undefined. |
packages/analytics-browser/test/utils/snippet-helper.test.ts |
Adds coverage for the undefined-queue behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…guards - Normalize `_q` and `_iq` on the pre-existing global so downstream code (e.g. the GTM wrapper that reads `amplitude._iq[name]` directly) doesn't crash after this bootstrap recovers. Per Codex review on PR #1763. - Widen `runQueuedFunctions` and `convertProxyObjectToRealObject` to accept `QueueProxy | undefined`, matching the runtime behavior added in this PR, and drop the unsafe cast in the regression test. Per Copilot review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Codex Review: Didn't find any major issues. Nice work! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
bugbot run |
There was a problem hiding this comment.
✅ 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 c7f45fe. Configure here.
Summary
Two
TypeErrors in the snippet bootstrap. Combined: 7,612 of 15,226 real SDK uncaught errors (~50%) over 7 days, surfaced via the SDK Diagnostics – Browser SDK dashboard.What's going wrong
When the bundle starts up it checks
window.amplitude.invokedto decide "the snippet ran, the queues_qand_iqare ready — drain them." Butinvoked = truecan be set without_q/_iqever existing. When that happens, the bootstrap crashes.This isn't really a snippet template bug — the template always seeds both queues. The bundle just trusts the global state too much. Most likely sources of the bad state:
window.amplitude = { invoked: true, track: noop }to neuter analytics. They don't know_iqis supposed to exist. Matches the heavy skew toward Instagram / Mobile Safari in-app browsers in the logs.invoked: trueand_qbut predates_iq, plus a newer<script src=...>bundle loaded directly. The wrapper wins the global.Fix: have the bootstrap normalize
_qand_iqitself instead of trusting them.The bugs
1.
Object.keys(amplitude._iq)at snippet-index.ts:52 — 6,167 events / 7dObject.keys(_iq) || []doesn't help:Object.keys(undefined)throws before||is ever evaluated.Datadog logs
{ "event_name": "sdk.error.uncaught", "app_id": "<redacted>", "sdk_tags": { "library": "amplitude-ts-sdk-script/2.39.0", "os_name": "Mobile Safari", "device_model": "iOS" }, "event_properties": { "error_name": "TypeError", "message": "TypeError: undefined is not an object (evaluating 'Object.keys(e.amplitude._iq)')", "stack_unminified": [ "at GlobalScope (packages/analytics-browser/src/snippet-index.ts:52:38)" ] } }2.
queue.lengthat snippet-helper.ts:29 — 1,445 events / 7dWhen iterating named instances,
instance._qcan beundefinedfor the same reason.convertProxyObjectToRealObjectnow returns early whenqueueis falsy.Datadog logs
{ "event_name": "sdk.error.uncaught", "app_id": "<redacted>", "sdk_tags": { "library": "amplitude-ts-sdk-script/2.39.0", "os_name": "Instagram", "device_model": "Android" }, "event_properties": { "error_name": "TypeError", "message": "Cannot read properties of undefined (reading 'length')", "stack_unminified": [ "at length (packages/analytics-browser/src/utils/snippet-helper.ts:29:28)", "at convertProxyObjectToRealObject (packages/analytics-browser/src/utils/snippet-helper.ts:21:2)", "at runQueuedFunctions (packages/analytics-browser/src/snippet-index.ts:58:6)" ] } }Customer impact
Each crash surfaces an uncaught
TypeErrorin the customer's browser console and any RUM / error-monitoring they run. The mainamplitudeobject IS set up before the crash, so directamplitude.track(...)calls after the snippet still work — but pre-load queued events on named instances are lost. After the fix, bootstrap completes cleanly.Changes
snippet-index.ts/gtm-snippet-index.ts— normalize_qand_iqon the global right afterObject.assign.snippet-helper.ts— return early whenqueueis falsy; widen the parameter type toQueueProxy | undefined.snippet-helper.test.ts— regression test for the undefined-queue case.Test plan
pnpm --filter @amplitude/analytics-browser test— 493 pass (was 492, +1 new test)pnpm --filter @amplitude/analytics-browser lint— clean (pre-existing warnings only)🤖 Generated with Claude Code