Sentryで検出された「Can't find variable: CONFIG」エラーの調査 ― サードパーティスクリプト起因の見極め方
Sentryで報告されたReferenceError「Can't find variable: CONFIG」の原因調査。自プロダクトのバグか、サードパーティスクリプト起因かを切り分ける方法を解説する。
発端:Sentryに見慣れないエラーが飛んできた
本番環境のSentryに以下のエラーが報告された。
ReferenceError: Can't find variable: CONFIG
スタックトレースは以下の通り。
updateGapFiller at line 311:46
updateFooterPositions at line 449:18
ページのURLは公開用のランディングページ。ユーザーがTwitter経由でアクセスした際に発生している。
一見すると自分たちのコードにバグがあるように見える。CONFIGという変数を定義し忘れたのでは? という疑いが最初に浮かぶ。
調査ステップ1:自プロダクトのコードを検索する
まず、コードベース全体でCONFIG、updateGapFiller、updateFooterPositionsをgrepした。
結果:該当なし。
プロジェクト内のどこにもこれらの識別子は存在しなかった。ランディングページのレンダリングに関わるコンポーネントも全て確認したが、これらの関数やグローバル変数を定義・参照している箇所はゼロ。
この時点で「自プロダクトのバグではない可能性が高い」と判断できる。
調査ステップ2:Sentryのメタデータを読む
エラーの詳細情報から、以下の手がかりが得られた。
| 項目 | 値 |
|---|---|
| browser | Twitter 11.71 |
| OS | iOS 16.3.1 |
| device | iPhone |
| mechanism | auto.browser.global_handlers.onerror |
| handled | false |
| Referer | https://t.co/ |
ここで重要なのは3つ。
1. ブラウザが「Twitter 11.71」
SafariでもChromeでもなく、Twitterアプリ内ブラウザ(In-App Browser) で発生している。iOSのIn-App BrowserはWKWebViewベースだが、アプリ側が独自のJavaScriptを注入することがある。
2. キャプチャメカニズムが global_handlers.onerror
Sentryが window.onerror 経由でキャッチしたエラー。つまり、自分たちのtry-catchで捕捉したエラーではなく、グローバルスコープで発生した未捕捉エラー。サードパーティスクリプトのエラーもここに流れてくる。
3. Refererが t.co
TwitterのURL短縮サービス経由のアクセス。ユーザーがTwitterのタイムライン上でリンクをタップし、Twitterアプリ内ブラウザで開いたことがわかる。
調査ステップ3:Breadcrumbsを確認する
Sentryのブレッドクラムを確認すると、エラー発生直前に以下のネットワークリクエストが記録されていた。
XHR GET score.im-apps.net/v1/fraud [200]
Fetch POST ad.doubleclick.net/activity [0]
Fetch POST www.google.com/ccm/collect [0]
これらはすべて広告トラッキング・不正検知系のサードパーティスクリプトによるリクエスト。自プロダクトのAPIコールではない。
結論:Twitterアプリ内ブラウザが注入したスクリプトのエラー
調査結果をまとめると、原因は以下の通り。
TwitterのiOSアプリ内ブラウザは、ページの表示を調整するために独自のJavaScriptを注入する。その中に updateGapFiller(ギャップフィラーの更新)や updateFooterPositions(フッター位置の更新)といったレイアウト調整用の関数が含まれている。
これらの関数が CONFIG というグローバル変数を参照するが、特定のバージョン(Twitter 11.71 / iOS 16.3.1)でこの変数の初期化が完了する前に関数が実行されてしまい、ReferenceError が発生する。
サードパーティスクリプト起因かを見極めるチェックリスト
今回の調査で得られた、エラーが自プロダクト由来かサードパーティ由来かを判断するためのチェックリストをまとめる。
| チェック項目 | 自プロダクト由来 | サードパーティ由来 |
|---|---|---|
| 関数名・変数名がコードベースに存在するか | 存在する | 存在しない |
| Sentryのmechanismは何か | generic / instrument | global_handlers.onerror |
| 特定のブラウザ・環境に限定されるか | 複数環境で再現 | 特定環境のみ |
| Breadcrumbsに自プロダクトのAPI呼び出しがあるか | 直前にある | サードパーティのリクエストのみ |
| エラーの行番号がソースマップで解決できるか | 解決できる | 解決できない |
対処法
このようなサードパーティスクリプト起因のエラーに対しては、以下の対処が有効。
Sentryの ignoreErrors でフィルタリング
typescriptSentry.init({ dsn: "...", ignoreErrors: [ // Twitter In-App Browserの注入スクリプト "Can't find variable: CONFIG", // その他よくあるサードパーティエラー "ResizeObserver loop limit exceeded", "ResizeObserver loop completed with undelivered notifications", ], });
denyUrls で特定ドメインのスクリプトを除外
typescriptSentry.init({ dsn: "...", denyUrls: [ /extensions\//i, /^chrome:\/\//i, /^moz-extension:\/\//i, ], });
beforeSend でより細かくフィルタリング
typescriptSentry.init({ dsn: "...", beforeSend(event) { const frames = event.exception?.values?.[0]?.stacktrace?.frames; if (frames?.some(frame => frame.function === "updateGapFiller" || frame.function === "updateFooterPositions" )) { return null; // このイベントを送信しない } return event; }, });
まとめ
Sentryに見慣れないエラーが飛んできたとき、まず疑うべきは「本当に自分たちのコードのバグか?」ということ。特に以下のケースではサードパーティスクリプト起因を強く疑う。
- コードベースにエラーメッセージの関数名・変数名が存在しない
- 特定のブラウザ(特にIn-App Browser)でのみ発生する
window.onerror経由でキャッチされている- Breadcrumbsが広告・トラッキング系のリクエストで埋まっている
ノイズを適切にフィルタリングすることで、本当に対処すべきエラーに集中できるようになる。