Same root cause as #587/#591 — Claude Code's runtime loader rejects array-form skills paths like ["./content-production", "./ai-seo", ...] even when each entry is a valid subdirectory containing SKILL.md. `claude plugin validate` accepts them but the loader does not. The proven canonical layout (used by self-improving-agent in #536): <plugin>/ ├── .claude-plugin/plugin.json skills: "./skills" └── skills/ ├── <skill-1>/SKILL.md ├── <skill-2>/SKILL.md └── ... Restructured 9 multi-skill domain plugins: - business-growth (4 skills moved) - c-level-advisor (28) - engineering (36) - engineering-team (32) - finance (2) - marketing-skill (43) - product-team (12) - project-management (8) - ra-qm-team (13) Also fixed standalone plugins that had root SKILL.md + ./skills/ subdir (agenthub, autoresearch-agent, executive-mentor, playwright-pro). The loader rejected them despite skills="./skills" because of the conflicting root SKILL.md (compare self-improving-agent which works because PR #536 moved its root SKILL.md). Moved each root SKILL.md into ./skills/<name>/. Restored standalone plugin folders to their original paths after the multi-skill restructure swept them into parent skills/ directories (marketplace.json source paths require original locations). Removed 7 orphaned marketplace entries that pointed to skill folders without their own plugin.json (content-creator, demand-gen, fullstack-engineer, aws-architect, product-manager, scrum-master, skill-security-auditor) — these were already non-functional. Bumped patch versions on every changed plugin and synced marketplace.json. Marketplace now lists 29 working plugins (down from 36). After merge: users run `/plugin marketplace update claude-code-skills` followed by `/plugin update --all` to pick up the working layout.
7.9 KiB
Tracking Debug Playbook
Step-by-step methodology for diagnosing and fixing analytics tracking issues.
The Debug Mindset
Analytics bugs are harder than code bugs because:
- They fail silently — no error thrown, just missing data
- They often only appear in production
- They can be caused by timing, consent, ad blockers, or just configuration
Work systematically. Don't guess. Verify at each layer before moving to the next.
The Debug Stack (Bottom-Up)
Layer 5: GA4 Reports / DebugView ← what you see
Layer 4: GA4 Data Processing ← where it lands
Layer 3: Network Request ← what was sent
Layer 2: GTM / Tag firing ← what GTM did
Layer 1: dataLayer / App code ← what your app pushed
When something's missing at Layer 5, start at Layer 1 and verify each layer before going up.
Tool Setup
GTM Preview Mode
- GTM → Preview (top right)
- Enter your site URL → Connect
- A blue bar appears at the bottom of your site: "Google Tag Manager"
- GTM Preview panel opens in a separate tab
- Perform the action you're debugging
- Check: did the expected tag fire?
Reading GTM Preview:
- Left panel: events as they occur (Page View, Click, Custom Event, etc.)
- Middle panel: Tags fired / Tags NOT fired for selected event
- Right panel: Variables values at the time of the event
GA4 DebugView
- GA4 → Admin → DebugView
- Enable debug mode via:
- GTM: add
debug_mode: trueto your GA4 Event tag parameters - Extension: install "GA Debugger" Chrome extension
- URL parameter: add
?_gl=or use GA4 debug parameter
- GTM: add
- Perform actions on your site
- Watch events appear in real-time (10-15 second delay)
Chrome DevTools — Network Tab
- Open DevTools → Network
- Filter by:
collectorgoogle-analyticsoranalytics - Perform the action
- Look for requests to
https://www.google-analytics.com/g/collect - Click the request → Payload tab → view parameters
Common Issues and Fixes
Issue: Event fires in GTM Preview but not in GA4
Possible causes:
-
Consent mode blocking — user is in denied state
- Check: In GTM Preview, look at Variables →
Analytics Storage— is itdenied? - Fix: Test with consent granted, or implement Advanced Consent Mode
- Check: In GTM Preview, look at Variables →
-
Filters blocking data — internal traffic filter is active
- Check: GA4 → Admin → Data Filters — is "Internal Traffic" filter active?
- Fix: Disable filter temporarily, test, then re-enable and exclude your IP correctly
-
Debug mode not enabled — DebugView only shows debug-mode traffic
- Check: Is
debug_mode: trueparameter on the GA4 Event tag? - Fix: Add it, or use the GA4 Debugger Chrome extension
- Check: Is
-
Wrong property — you're looking at a different GA4 property
- Check: Confirm Measurement ID in GTM matches the GA4 property you're viewing
- Fix: Compare
G-XXXXXXXXXXin GTM vs. GA4 Data Stream settings
-
Duplicate GA4 configuration tags — two config tags = double sessions + weird data
- Check: GTM → Tags → filter by "GA4 Configuration" — more than one?
- Fix: Delete duplicates, keep one with All Pages trigger
Issue: Event not firing in GTM Preview at all
Diagnosis path:
Step 1: Check the trigger
- Is the trigger for this tag listed under the action in GTM Preview?
- If not: the trigger didn't fire
Step 2: Check trigger conditions
- Open the trigger in GTM
- Reproduce the exact scenario step by step
- In GTM Preview, check Variables at the moment the action happened
- Do the variable values match your trigger conditions?
Step 3: dataLayer issue (for Custom Event triggers)
- In GTM Preview → select the relevant event in left panel → Variables tab
- Scroll to find
event— what's the value? - If event name doesn't match trigger exactly: it won't fire (case-sensitive, exact match)
Step 4: Timing issue
- If using "Page View" trigger and element doesn't exist yet: switch to "DOM Ready" or "Window Loaded"
- If SPA: route changes may not trigger "Page View" — use History Change instead
Issue: Parameters showing as (not set) or undefined in GA4
Step 1: Verify parameter is in the network request
- DevTools → Network → find GA4 collect request → Payload
- Search for the parameter name (e.g.,
plan_name) - If not there: GTM variable isn't resolving correctly
Step 2: Check the GTM variable
- GTM Preview → find the event → Variables tab
- Find the variable for this parameter (e.g.,
DLV - plan_name) - What's its value? If
undefined: the dataLayer push didn't include this key, or key name is wrong
Step 3: Check dataLayer push in your app code
- DevTools → Console → type:
dataLayer.filter(e => e.event === 'your_event_name') - Inspect the object — is the parameter key present and spelled correctly?
Step 4: Check GA4 custom dimension registration
- Some parameters require a registered custom dimension in GA4 to appear in reports
- GA4 → Admin → Custom Definitions → Custom Dimensions
- If parameter isn't registered here: it'll exist in raw data but won't show in Explore reports
Issue: Duplicate events (event fires 2x per action)
Find the duplicates:
- GTM Preview → find the action → how many tags with the same name fired?
- DevTools → Network → filter by
collect→ count hits for the action
Common causes:
-
Enhanced Measurement + manual GTM tag
- e.g., Enhanced Measurement tracks outbound clicks, GTM also has an outbound click tag
- Fix: disable the Enhanced Measurement setting OR remove the GTM tag
-
Two GTM Configuration tags
- Each sends its own hits
- Fix: delete one, keep one
-
SPA router fires pageview + History Change trigger also fires
- Fix: disable Enhanced Measurement pageview, use only History Change tag
-
Event fires on multiple triggers that both match
- Fix: make triggers more specific — add exclusion conditions
Issue: Sessions/users look wrong (too high or too low)
Too many sessions:
- Multiple GA4 Configuration tags
- History Change trigger firing + Enhanced Measurement pageview on SPA
- Client ID not persisting (cookie being blocked or cleared)
Too few sessions / users:
- Consent blocking analytics for non-consenting users (expected under strict consent mode)
- Bot filtering too aggressive
- GA4 tags firing on wrong pages only
Sessions reset unexpectedly (user shows as new on every page):
- Cross-domain tracking not configured
- Cookie domain mismatch
- GTM cookie settings incorrect
Issue: Conversions not matching between GA4 and Google Ads
Check 1: Attribution window mismatch
- GA4 default: 30-day last click
- Google Ads: check conversion action settings for window
- These legitimately produce different numbers
Check 2: Conversion event names
- In Google Ads → Tools → Conversions → imported from GA4
- Does the linked event name exactly match the GA4 event?
Check 3: Import is linked
- Google Ads → Tools → Linked Accounts → Google Analytics 4
- Is the correct GA4 property linked and synced?
- Sync can take 24-48 hours after changes
Check 4: Enhanced Conversions
- If GA4 uses a user_id or email parameter, Enhanced Conversions can improve matching
- Google Ads → Conversions → Enhanced Conversions for Web → Enable
Debug Checklist Template
Use this for any new tracking issue:
[ ] Confirmed exact event name and parameters expected
[ ] Verified app code is pushing to dataLayer (console: dataLayer)
[ ] GTM Preview: trigger fires at correct moment
[ ] GTM Preview: parameters resolve to correct values (not undefined)
[ ] Network: GA4 collect request appears with correct payload
[ ] GA4 DebugView: event appears within 30 seconds
[ ] GA4 DebugView: parameters present and correct
[ ] GA4 Reports: event appears (24-48h delay for standard reports)
[ ] Consent check: tested with analytics consent granted
[ ] Filter check: internal traffic filter not blocking test traffic