Confirmed production defect (discovered during #161 testing)
On a fresh browser profile at https://www.seasaba.com, with no interaction with the Cookiebot banner (Cookiebot state: hasResponse: false, statistics/marketing false):
| Observed pre-consent |
Evidence |
clarity.ms/tag/xq4re63wsc?ref=gtm + scripts.clarity.ms/0.8.70/clarity.js download |
Clarity tag fires unconditionally |
c.clarity.ms/c.gif, {b,g,o,u}.clarity.ms/collect |
Clarity telemetry sent |
_clck, _clsk on .seasaba.com with real values |
Clarity first-party cookies |
bat.bing.com/bat.js, bat.bing.com/action/0?...evt=pageLoad |
UET base tag fires + pageLoad beacon |
_uetsid, _uetvid on .seasaba.com with real GUIDs (e.g. db220b30b84d11f1…), plus localStorage._uetsid/_uetvid(+_exp) |
UET advertising identifiers written |
MUID, CLID, ANONCHK, SM, MR on .clarity.ms/.bing.com |
Microsoft third-party device-ID cookies |
connect.facebook.net/fbevents.js, facebook.com/tr PageView, fr cookie |
Meta Pixel fires unconditionally |
www.clarity.ms/tag/uet/187264257?conversions=1&insights=1 |
Clarity additionally loads its own UET integration (linked to UET tag 187264257) |
Reproduced on Chromium 153 and WebKit (Playwright, fresh contexts). On WebKit ITP blocked the third-party cookies but all first-party cookies/localStorage and all telemetry requests still occurred.
Decline is too late: clicking Decline makes Cookiebot remove the first-party tracker cookies, but third-party cookies persist, already-loaded Clarity keeps calling *.clarity.ms/collect after decline, and the telemetry has already been sent. Consent must gate the tag load, not clean up after it.
Root cause
Verified by decoding the compiled live container GTM-5PFMJFN (gtm.js):
- No tag in the container has an Additional Consent Check — zero
consent metadata on all 26 compiled tags.
gtm.init_consent (Consent Initialization) fires only the Cookiebot CMP tag (__cvt_57SF4, CBID 25085b0a-…, consentModeEnabled on) — correct, and Consent Mode defaults ARE established before other tags (gcs=G100 denied defaults on the first GA4 collect; pscdl=denied).
gtm.init fires GA4 config + the Clarity tag (__cvt_MQDKZ, project xq4re63wsc) — Clarity has no native Consent Mode support, so it loads regardless of the denied default.
gtm.js (All Pages) fires the UET base tag (__baut, tag 187264257) and a custom HTML tag = Meta Pixel (fbq init 23854267411660080, PageView) — both ungated.
- Ten
__baut UET event tags (tag_id 52–61) mirror the GA4 custom events and the purchase tag (tag_id 67) is bound to booking-page DOM events — all ungated.
Why _uetsid/_uetvid got real identifiers instead of null|… placeholders: Microsoft's sanctioned denied-mode writes only placeholder values. We observed real GUIDs and no evt=consent/asc=D signal to bat.bing.com pre-consent — the denied state never reaches UET. The __baut template can push consent default {source:'gtm_default'}, but that source reads dataLayer consent entries; the Cookiebot template sets defaults via the sandbox setDefaultConsentState API (no dataLayer entry — confirmed: zero consent pushes observed before a user choice). So bat.js initializes unrestricted.
Isolation test (all clarity.ms blocked): _uetsid/_uetvid still written → GTM's own __baut tag is independently defective, not just Clarity's UET loader.
What works correctly: GA4/Google Ads honor Consent Mode natively — denied pings only (gcs=G100), no _ga/_gcl_au until consent granted. Cookiebot defaults precede all tag execution — no race.
Remediation (GTM dashboard — no repo code change required)
The repo only loads gtm.js (components/analytics-loader.tsx); the consent boundary belongs inside GTM. docs/COOKIEBOT_CONSENT_SETUP.md §2.4–2.7 already prescribes this configuration — it was documented but never applied/published.
- Clarity tag (
__cvt_MQDKZ, project xq4re63wsc): add Additional Consent Check analytics_storage (Statistics, per documented mapping). Blocks clarity.js, _clck/_clsk, collect calls, and Clarity's tag/uet loader.
- UET consent default — create custom HTML tag on Consent Initialization – All Pages:
window.uetq = window.uetq || []; window.uetq.push('consent','default',{ad_storage:'denied'}); This queues denied before bat.js init → placeholder-only cookies + cookieless modeling (fixes the real-identifier writes while keeping Microsoft's Advanced Consent Mode / avoiding "Need attention"). Alternative, stricter: also add ad_storage consent check to the UET base tag — no bat.js at all pre-consent, but Microsoft loses the denied signal. Choose deliberately.
- UET base tag (
187264257): verify the template's "Inherit initial consent"/gtm_default option is enabled; confirm with UET Tag Helper that asc=D appears pre-consent.
- UET event tags (tag_ids 52–61) + purchase tag (67): add Additional Consent Check
ad_storage (Marketing).
- Meta Pixel custom HTML tag (62): add Additional Consent Check
ad_storage (blocks fbevents.js, PageView, fr).
- Publish a new GTM version after verifying in Preview/Tag Assistant.
Verification (deterministic)
scripts/consent-audit.mjs (added in the companion docs/tooling PR) replays the three states against production and prints cookies/storage/requests:
- No interaction / Declined: no
clarity.ms or bat.bing.com requests, no _clck/_clsk/_uetsid/_uetvid, no MUID/CLID/fr, empty _uet* localStorage. UET pageLoad shows asc=D only if the base tag is intentionally left ungated under option 2 (placeholders null| acceptable per Microsoft docs — decide and record).
- Granted:
_clck, _ga, _uetsid, fr appear; evt=consent…asc=G fires to bat.bing.com; Clarity collects normally.
- Regression: GA4 denied-pings-only pre-consent (
gcs=G100, no _ga); Respond.io widget unaffected; Checkfront booking ungated.
Acceptance criteria
Refs: discovered in #161 (independent of the harmless Clarity ICU exception); consent architecture per docs/COOKIEBOT_CONSENT_SETUP.md.
Generated with Devin
Confirmed production defect (discovered during #161 testing)
On a fresh browser profile at
https://www.seasaba.com, with no interaction with the Cookiebot banner (Cookiebot state:hasResponse: false, statistics/marketingfalse):clarity.ms/tag/xq4re63wsc?ref=gtm+scripts.clarity.ms/0.8.70/clarity.jsdownloadc.clarity.ms/c.gif,{b,g,o,u}.clarity.ms/collect_clck,_clskon.seasaba.comwith real valuesbat.bing.com/bat.js,bat.bing.com/action/0?...evt=pageLoad_uetsid,_uetvidon.seasaba.comwith real GUIDs (e.g.db220b30b84d11f1…), pluslocalStorage._uetsid/_uetvid(+_exp)MUID,CLID,ANONCHK,SM,MRon.clarity.ms/.bing.comconnect.facebook.net/fbevents.js,facebook.com/tr PageView,frcookiewww.clarity.ms/tag/uet/187264257?conversions=1&insights=1187264257)Reproduced on Chromium 153 and WebKit (Playwright, fresh contexts). On WebKit ITP blocked the third-party cookies but all first-party cookies/localStorage and all telemetry requests still occurred.
Decline is too late: clicking Decline makes Cookiebot remove the first-party tracker cookies, but third-party cookies persist, already-loaded Clarity keeps calling
*.clarity.ms/collectafter decline, and the telemetry has already been sent. Consent must gate the tag load, not clean up after it.Root cause
Verified by decoding the compiled live container
GTM-5PFMJFN(gtm.js):consentmetadata on all 26 compiled tags.gtm.init_consent(Consent Initialization) fires only the Cookiebot CMP tag (__cvt_57SF4, CBID25085b0a-…,consentModeEnabledon) — correct, and Consent Mode defaults ARE established before other tags (gcs=G100denied defaults on the first GA4 collect;pscdl=denied).gtm.initfires GA4 config + the Clarity tag (__cvt_MQDKZ, projectxq4re63wsc) — Clarity has no native Consent Mode support, so it loads regardless of the denied default.gtm.js(All Pages) fires the UET base tag (__baut, tag187264257) and a custom HTML tag = Meta Pixel (fbq init 23854267411660080,PageView) — both ungated.__bautUET event tags (tag_id52–61) mirror the GA4 custom events and the purchase tag (tag_id67) is bound to booking-page DOM events — all ungated.Why
_uetsid/_uetvidgot real identifiers instead ofnull|…placeholders: Microsoft's sanctioned denied-mode writes only placeholder values. We observed real GUIDs and noevt=consent/asc=Dsignal tobat.bing.compre-consent — the denied state never reaches UET. The__bauttemplate can pushconsent default {source:'gtm_default'}, but that source readsdataLayerconsent entries; the Cookiebot template sets defaults via the sandboxsetDefaultConsentStateAPI (nodataLayerentry — confirmed: zero consent pushes observed before a user choice). So bat.js initializes unrestricted.Isolation test (all
clarity.msblocked):_uetsid/_uetvidstill written → GTM's own__bauttag is independently defective, not just Clarity's UET loader.What works correctly: GA4/Google Ads honor Consent Mode natively — denied pings only (
gcs=G100), no_ga/_gcl_auuntil consent granted. Cookiebot defaults precede all tag execution — no race.Remediation (GTM dashboard — no repo code change required)
The repo only loads
gtm.js(components/analytics-loader.tsx); the consent boundary belongs inside GTM.docs/COOKIEBOT_CONSENT_SETUP.md§2.4–2.7 already prescribes this configuration — it was documented but never applied/published.__cvt_MQDKZ, projectxq4re63wsc): add Additional Consent Checkanalytics_storage(Statistics, per documented mapping). Blocksclarity.js,_clck/_clsk, collect calls, and Clarity'stag/uetloader.window.uetq = window.uetq || []; window.uetq.push('consent','default',{ad_storage:'denied'});This queuesdeniedbeforebat.jsinit → placeholder-only cookies + cookieless modeling (fixes the real-identifier writes while keeping Microsoft's Advanced Consent Mode / avoiding "Need attention"). Alternative, stricter: also addad_storageconsent check to the UET base tag — no bat.js at all pre-consent, but Microsoft loses the denied signal. Choose deliberately.187264257): verify the template's "Inherit initial consent"/gtm_defaultoption is enabled; confirm with UET Tag Helper thatasc=Dappears pre-consent.ad_storage(Marketing).ad_storage(blocksfbevents.js,PageView,fr).Verification (deterministic)
scripts/consent-audit.mjs(added in the companion docs/tooling PR) replays the three states against production and prints cookies/storage/requests:clarity.msorbat.bing.comrequests, no_clck/_clsk/_uetsid/_uetvid, noMUID/CLID/fr, empty_uet*localStorage. UET pageLoad showsasc=Donly if the base tag is intentionally left ungated under option 2 (placeholdersnull|acceptable per Microsoft docs — decide and record)._clck,_ga,_uetsid,frappear;evt=consent…asc=Gfires tobat.bing.com; Clarity collects normally.gcs=G100, no_ga); Respond.io widget unaffected; Checkfront booking ungated.Acceptance criteria
ad_storagedenied before bat.js init (no real_uetsid/_uetvid/MUIDvalues pre-consent)Refs: discovered in #161 (independent of the harmless Clarity ICU exception); consent architecture per
docs/COOKIEBOT_CONSENT_SETUP.md.Generated with Devin