Skip to content
Back to skills

Tracking Health

ASecurity

Preventive audit of conversion tracking across all configured ad platforms — Meta pixels + CAPI, Google Ads conversion actions, and final-URL tracking-parameter consistency on every platform — with a GA4 cross-check. Use when the user asks to check tracking, audit conversion measurement, verify pixels / tags, check that ads carry the right utm / tracking parameters for the campaign they are in, diagnose why conversions stopped or look wrong, sanity-check CV counting, or requests a 計測ヘルスチェック /...

  • 47 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 24, 2026
ai-agentsgoshellbashrailsapisecurityperformance

Works with

  • claude code
  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned October 2, 2026

npx -y skills add logly/mureo --skill tracking-health --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Tracking Health?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Tracking Health
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/logly-tracking-health/badge)](https://www.skillsdirectory.com/skills/logly-tracking-health)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: tracking-health
description: "Preventive audit of conversion tracking across all configured ad platforms — Meta pixels + CAPI, Google Ads conversion actions, and final-URL tracking-parameter consistency on every platform — with a GA4 cross-check. Use when the user asks to check tracking, audit conversion measurement, verify pixels / tags, check that ads carry the right utm / tracking parameters for the campaign they are in, diagnose why conversions stopped or look wrong, sanity-check CV counting, or requests a 計測ヘルスチェック / タグ・計測監査 / パラメータ整合性チェック / 計測が壊れていないか確認. Reads STRATEGY.md and STATE.json, produces a per-platform tracking scorecard (OK / Watch / Broken per check) and a fix list ranked by revenue risk."
metadata:
  version: 0.21.3
---

# Tracking Health

> PREREQUISITE: Read `../_mureo-shared/SKILL.md` for auth, security rules, output format, and **Tool Selection** (Read/Write on Code, `mureo_strategy_*` / `mureo_state_*` MCP on Desktop / Cowork).

Preventively audit conversion tracking across every configured platform. Measurement breakage is the costliest **silent** failure in ad ops — spend keeps flowing while the signal the algorithm optimizes toward is wrong or absent — so this skill hunts for it *before* it shows up as a mysterious CPA spike.

## Prerequisites
- STRATEGY.md and STATE.json must exist in the current directory (run the `onboard` skill first if not)

## Steps

**Before you start**: Run the **Diagnostic preamble** from ../_mureo-shared/SKILL.md — load learning insights (mureo_learning_insights_get) and consult advisors (mureo_consult_advisor) before drawing conclusions.

0. **Establish today**: call `mureo_state_get` **first, on every host** (including Claude Code, where you would otherwise `Read` the file) and take `server_now` from its response — ISO 8601 with UTC offset, e.g. `2026-07-28T10:12:33+09:00`. Its date is the **only source of the current date** for this run: judging whether a pixel's `last_fired_time` is "recent" is a comparison against it, and the `observation_due` you write in step 12 is `server_now`'s date + 7 days. Do not shell out (this skill must run in Bash-less headless hosts) and do not read the date off STATE.json — `last_synced_at`, `reports.*.period` and `action_log` timestamps are **history**, never evidence of what day it is now. **Never write `server_now` into STATE.json**: it is a response field, and a persisted copy becomes tomorrow's stale "today".

1. **Load context**: Read STRATEGY.md (especially each Goal's declared conversion definition, Persona, and Operation Mode) and STATE.json (the same `mureo_state_get` response from step 0 on MCP hosts). Note **what the operator considers a real conversion** for each platform (a lead form submit, a purchase, a phone call) — the audit below is only meaningful relative to that intent.

2. **Discover available platforms**: Identify all configured platforms from STATE.json `platforms` and which data sources (GA4, Search Console) are accessible. Also enumerate installed **plugin** platforms (`mcp__mureo__<plugin>_*` tools) and any **hosted official-MCP connector** present in the session (e.g. TikTok's `tt-ads-*` tools, STATE.json key `tiktok_ads`); include them best-effort — see `../_mureo-shared/SKILL.md` → *Plugin platforms* and *Hosted-connector platforms*.

3. **Meta Ads — pixel inventory + health** (prefer mureo native):
   - **Inventory**: call `meta_ads_pixels_list` to enumerate every pixel in the account (id, name, `last_fired_time`). Zero pixels on an account running conversion campaigns is a **Broken** finding on its own.
   - **Per-pixel health**: for each pixel call `meta_ads_pixels_get` and check `last_fired_time` is recent (a pixel that stopped firing = **Broken**). Call `meta_ads_pixels_stats` (window `last_30d`, widen to `last_90d` to catch slow degradations) and look for a sudden drop or a flatline in PageView / Lead / Purchase volume — a step-down that lines up with a site deploy is the classic pixel break.
   - **Event coverage**: call `meta_ads_pixels_events` to list the distinct standard + custom events the pixel actually receives, with sample parameters. Flag when the event the operator's Goal depends on (e.g. `Lead`, `Purchase`) is **absent** or firing with missing parameters (no `value` / `currency` on a Purchase).
   - **Per-campaign `result_indicator` audit**: call `meta_ads_insights_report` (level `campaign`) and inspect each campaign's `result_indicator`. Flag any campaign optimized for `actions:link_click` where the operator expects real leads / purchases (`actions:offsite_conversion.fb_pixel_lead`) — that campaign is buying clicks, not conversions, and its "results" number is not comparable to a lead-optimized campaign's. This CV-definition-mismatch detection is a mureo value-add; see daily-check step 4.
   - **CAPI presence check**: mureo ships **no dedicated tool** that reports a browser-vs-server event split, so treat this as best-effort. Use `meta_ads_pixels_events` to see whether server-originated (Conversions API) events appear alongside browser events, and note in the scorecard when a high-value account is relying on the browser pixel alone (no CAPI) — a resilience risk as browser signal degrades. mureo *can send* server events (`meta_ads_conversions_send` / `meta_ads_conversions_send_lead` / `meta_ads_conversions_send_purchase`), but a definitive server/browser coverage split still requires Events Manager; say so rather than guessing.
   - If mureo's Meta Ads tools are unavailable (i.e. `MUREO_DISABLE_META_ADS=1` after `mureo providers add meta-ads-official`), fall back to the official `meta-ads-official` hosted MCP for whatever pixel / insights surface it exposes, and note that the `result_indicator` CV-mismatch analysis is a mureo-specific value-add absent from the official MCP.

4. **Google Ads — conversion actions + status** (prefer mureo native):
   - **Inventory + status**: call `google_ads_conversions_list` to list every conversion action with its `status` (`ENABLED` / `HIDDEN` / `REMOVED`) and `category` (`PURCHASE`, `SIGNUP`, …). For any action whose configuration you need to inspect further, call `google_ads_conversions_get`. A campaign optimizing toward a `REMOVED` / `HIDDEN` action, or an account with **no** ENABLED conversion action, is a **Broken** finding.
   - **Recent-data check**: call `google_ads_conversions_performance` (period `LAST_30_DAYS`) — only rows with conversions > 0 are returned, so an ENABLED action that is **absent** from the report has recorded **no recent conversions**. Cross-reference its `first_date` / `last_date`: an action that used to fire and went silent is a likely tag break; a brand-new action with no data yet is expected. Flag silent actions as **Watch** (or **Broken** if it is the primary action a live campaign depends on).
   - **Primary / secondary counting sanity**: Google Ads counts only *primary* actions into the "Conversions" column that bidding optimizes toward; *secondary* actions are observational. mureo's read tools do not expose the primary/secondary flag directly, so verify it in the Google Ads UI and flag the common misconfigurations: (a) a micro-conversion (e.g. `PageView`) set as primary — inflates Conversions and misleads Smart Bidding; (b) the true macro-conversion set as secondary — starves bidding of the signal. Also flag **double-counting** risk when two actions plausibly fire on the same event.
   - If mureo's Google Ads tools are unavailable (`MUREO_DISABLE_GOOGLE_ADS=1` after `mureo providers add google-ads-official`), fall back to the official `google-ads-official` MCP for the conversion-action listing / performance surface it exposes, and note that the recency and counting-sanity heuristics still apply.

5. **Cross-check with GA4** (when available): mureo ships **no native GA4 tools** — GA4 is reached as a configured data source, so pull the site's conversion count from that source (do not reference a `ga4_*` tool that does not exist). Compare each platform's *ads-reported* conversions against GA4's conversions for the same event and window. A divergence **> 20 %** is a **flag** — report the likely causes so the operator can triage:
   - **Attribution window / model** differences (platform click-through + view-through vs GA4 last-click).
   - **Consent Mode / cookie loss** suppressing GA4 (or platform) tags for a slice of users.
   - **Tag coverage gaps** — the pixel/tag missing on some conversion pages, or duplicated on others.
   - **Cross-device / logged-in attribution** the platform can resolve but GA4 cannot.
   Present the discrepancy as a *signal to investigate*, not proof one number is wrong — the platforms genuinely count differently.

6. **Search Console**: N/A for conversion tracking — Search Console has no conversion signal. Skip it here (organic conversion behavior belongs in daily-check / weekly-report).

7. **Plugin & hosted-connector platforms** (best-effort): for a plugin platform (`plugin:<dist>:<provider>`) or a hosted connector (`tiktok_ads`), report only the basic tracking status its own tools expose and emit `analytics_not_available_for_<platform>` for the mureo-only deep checks (pixel health, `result_indicator`, conversion-action recency) — do **not** fabricate a tracking verdict from an integration's tool schemas. See `../_mureo-shared/SKILL.md` → *analytics-module parity* and *Hosted-connector platforms*. The one exception is step 8's tracking-parameter consistency check: it is platform-neutral and analyses ad records **you** pass in rather than a per-platform analytics module, so it applies to a plugin or hosted platform exactly as it does to Google and Meta — whenever that platform's own tools can list ads with their destination URLs.

8. **Tracking-parameter consistency — every platform** (`analysis_tracking_consistency_check`): steps 3-7 ask whether the *conversion signal* arrives. This step asks whether the click that produced it lands in the right **row** of the analytics everyone downstream reads. An ad uploaded into campaign B carrying campaign A's `utm_campaign` breaks segment-level reporting while delivery, spend and conversions all look perfectly healthy — nothing appears broken, so nobody investigates.

   - **Collect ad records** for every platform you enumerated in step 2, into one platform-neutral list. Each record is `{ad_id, campaign_id, campaign_name, platform, final_urls, impressions}`:
     - **Google Ads**: `google_ads_ads_list` returns `final_urls`, `campaign_id` and `campaign_name` directly.
     - **Meta Ads**: `meta_ads_ads_list` — the link is in `creative.object_story_spec` (`link_data.link`, or the call-to-action link on `video_data` / `photo_data` / `template_data`), and `creative.url_tags` is appended to it at delivery time, so pass the link **with** `url_tags` merged onto its query string.
     - **Plugin platforms**: use the plugin's own ad-listing tool and its `final_url` field; pass the canonical `plugin:<dist>:<provider>` key as `platform` (see `../_mureo-shared/SKILL.md`).
     - **Bridged / hosted connectors**: pass whatever destination URL the connector reports. **The Amazon Ads bridge exposes no destination-URL field** — pass those ads with `final_urls: []` so they are reported as *unchecked* rather than clean.
     - **`impressions` is what grades the finding**: supply it per ad (a per-ad performance report over the audited window) so mureo can tell a served mis-tagged ad — a data-integrity incident needing a reporting caveat — from one that never served, a cheap fix. Omit it only when the platform cannot give it; omitted is **not** the same as `0`.
   - **Declared convention**: if STRATEGY.md has a `## Tracking Convention` section, pass that text as `convention_markdown` **verbatim**. Do not interpret it yourself — mureo parses it. Without one, the zero-configuration checks still run.
   - **Report what comes back, and only that.** Findings carry `severity`, `delivery_state`, the ad ids and the evidence (which parameters differ, which campaign the borrowed scheme belongs to, which landing page two schemes collided on). mureo deliberately does **not** say which of two schemes is correct — put both in front of the operator. Never add a finding of your own by eyeballing utm values: guessing an account's convention is the failure this check replaces. In particular, only `utm_source` / `utm_medium` / `utm_campaign` identify a campaign; `utm_content` and `utm_term` vary per creative and per keyword by design and are deliberately not compared, so do not report them as inconsistencies.
   - **`ads_without_readable_url` is not a pass.** List those ads explicitly as *not checked* on the platform they came from, and carry the tool's `notes` into the scorecard evidence.
   - Full list of what this check can and cannot detect: `docs/tracking-consistency.md`.

9. **Scorecard**: present a per-platform **tracking scorecard**, one row per check, each rated **OK** / **Watch** / **Broken**:
   ```
   Platform    Check                              Status   Evidence
   Meta Ads    Pixel firing (last_fired_time)     OK       fired 2h ago
   Meta Ads    Lead event present                 Broken   no Lead events in 30d
   Meta Ads    result_indicator vs Goal           Watch    2 campaigns on link_click
   Meta Ads    CAPI present                        Watch    browser pixel only
   Meta Ads    URL tracking consistency           OK       0 findings / 34 ads
   Google Ads  Primary conversion action ENABLED  OK       "Purchase" ENABLED
   Google Ads  Recent conversion data             Broken   0 conv in 30d for "Lead"
   Google Ads  Primary/secondary counting         Watch    verify PageView not primary
   Google Ads  Ads CV vs GA4 CV (±20%)            Watch    Ads 120 / GA4 78 (+54%)
   Google Ads  URL tracking consistency           Broken   16 ads carry campaign A's utm_campaign
   Amazon Ads  URL tracking consistency           n/a      not checked — bridge exposes no URL
   ```
   Rate the consistency row **Broken** when any finding has `delivery_state: served` (reporting is already wrong), **Watch** when findings exist but nothing served, and **OK** only when the platform's ads were all readable and produced no findings.

10. **Fix list — ranked by revenue risk**: turn every **Broken** / **Watch** into a concrete fix, ordered by how much spend is riding on the broken signal (a broken primary action on the highest-spend campaign is #1; a missing secondary action on a paused campaign is last). For each fix state: what is wrong, the concrete remediation, and the platform/UI or tool that performs it.

11. **Approval gate for any state-mutating fix**: most remediations here are **operator actions** (re-add a tag on the site, flip an action to primary in the Google Ads UI, install CAPI). Where a fix *does* mutate platform state through a mureo tool (e.g. `google_ads_conversions_update` to re-enable an action, `google_ads_conversions_create` to add one, sending backfill events via `meta_ads_conversions_send`), apply the *Confirm Before Write Operations* rule from `../_mureo-shared/SKILL.md` — show the current value and the proposed change and get explicit **approval** before the call. For an official/hosted-MCP write, also self-apply the STRATEGY.md `## Guardrails` rules yourself (mureo's PolicyGate cannot see that call) — see `../_mureo-shared/SKILL.md` → *Hosted-connector platforms*. A plugin / bridged write (including Amazon Ads) does reach the gate — **exactly** on the money tools mureo declares paths for (the 13 money-carrying Amazon tools), with the pattern scan still applied underneath as a floor, and best-effort on argument-key patterns alone for anything else — so still state the applicable cap.

12. **Record any change to `action_log`**: for each approved state-mutating fix, log to `action_log` (via `mureo_state_action_log_append`) with `metrics_at_action` (the pre-fix conversion counts / firing state you could read) and an `observation_due` **7 days** out from `server_now`'s date, so daily-check's evidence step confirms the fix actually restored the signal. A pure audit with no changes still logs a summary entry (findings + scorecard verdict), with no `observation_due`. **Give every entry a display line**: `display_title` (at most 40 characters) and `display_summary` (at most 120) — what this action WAS, in the operator's words, as plain text with no markdown (`**bold**` reaches a person as asterisks). The dashboard row shows those two and stops there; the full `summary` is drill-down only, so keep writing it as fully as the next agent needs and let the display line be the short one. Over either bound the append is refused, never truncated.

13. **Persist the report summary** (best-effort): Call `mureo_state_report_set` with `report="tracking"` and a concise `summary` object so the read-only dashboard can render this audit without re-running you. Follow this convention:
    - `generated_at`: ISO 8601 timestamp of this run — use `server_now`
    - `period`: the window audited (e.g. `"LAST_30_DAYS"`)
    - `totals`: the account's headline figures, using the canonical metric vocabulary — `spend`, `conversions`, `cpa`, `ctr`, `clicks`, `impressions`. This is the block the dashboard renders **as figures**. **Raw numbers only**: `773957`, not `"¥773,957"`; `0.0466`, not `"4.66%"` — one of those keys carrying a string is refused, because it sits where the view reads a figure and renders as nothing. A key outside the vocabulary may ride along: it is stored, just not shown as a headline number. Omit `totals` entirely if this run gathered no account-level figures.
    - `kpis`: the per-platform breakdown, not the headline row — tracking health counts (e.g. `{"meta_ads": {"pixels_ok": 1, "broken": 1}}`), the tracking-parameter consistency counts from step 8 (`{"google_ads": {"tracking_findings": 3, "ads_unchecked": 0}}`) and any ads-vs-GA4 divergence
    - `flags`: notable items (e.g. `["meta_lead_event_missing", "google_primary_action_zero_conv", "tracking_parameter_mismatch"]`)
    - `narrative`: the overall verdict (OK / Watch / Broken) and what you propose next, **at most 400 characters** — the tool refuses a longer one rather than truncating it (a sentence cut in half is worse than a long one). Do not restate the figures and do not list the findings here: numbers belong in `totals`, findings in `flags`.

    **Reflect the FINAL state, and persist this LAST** — after every `action_log` entry and any fix you applied this run. This is best-effort: if `mureo_state_report_set` is unavailable (e.g. a pure file-mode host without the context MCP), skip it silently — the rest of this skill still works.

14. **Persist the display contract** (best-effort): Call `mureo_state_display_set` with what the DASHBOARD shows. This is a different audience from the summary above — STATE.json is written for the next agent, and this small bounded section is the only thing the operator's screen reads. Write it in the SAME pass as the report, from the SAME figures, and **reach no new verdict here**: every value below is one you already decided, and this step only renders it.
    - `source`: your own skill name — REQUIRED whenever you write any section, at most 24 characters. The contract is replaced wholesale by whoever writes it last, so this is what lets the card say whose answer it is showing. `generated_at` is stamped by the server — do not compute it.
    - `nav_message`: the ONE line the operator should act on today — what the signal needs, e.g. `"Meta pixel has not fired in 6 days — verify the tag on the thanks page"`. One line, not a summary of the report.
    - `highlights`: at most 3 chips, and the `tone` must match the verdict you already gave — `good` for a target met or a clear win, `watch` for something to keep an eye on, `bad` for something deteriorating. Choose the three that matter; a fourth is refused and nothing is dropped for you. Map a finding's severity to a chip tone: action → bad / watch → watch / positive → good. info does NOT become a highlight — a neutral note would spend one of the 3 chips an action or a win needed, and it is still in the report for whoever wants it.
    - `breakdown.campaigns` / `.adgroups`: one row per entity you judged, carrying figures you ALREADY hold — `spend`, `mcpa` (the measured CPA) and `target_cpa` — plus `state` from the closed set (`target_met` / `improving` / `watch` / `worsening` / `no_data`) and a `note` of at most 40 characters. This audit judges tags and conversion actions rather than spend, so it normally omits `breakdown` entirely — write rows only where you actually judged campaigns or ad sets, and never invent a `spend` column to fill the table. **Omit a figure you do not have**: `0` states a perfect CPA rather than the absence of one, and `no_data` is the state for an entity with too little delivery to judge.
    - `proposals`: the recommendations you did NOT carry out, one entry each — `title` (what to do) plus `body` (why, in one line) plus `date` = `server_now`'s date as `YYYY-MM-DD`. Something you DID apply this run is the same entry with `status: "done"`.
    - `stated_values`: label + figure chips ONLY (`{"label": "…", "value": …}`) — the scorecard figures: `{"label": "checks passed", "value": "7 of 9"}`, `{"label": "days since fire", "value": 6}`. **No prose notes here.** A sentence lands in a numeric column and the tool refuses it; anything that needs a sentence stays in the report's `narrative` above, exactly where it goes today.

    The bounds, verbatim from the tool: The dashboard reads THIS section and nothing else — keep your reasoning where it already goes. Every bound below refuses the write rather than truncating it, because a sentence cut in half is worse than a long one. nav_message: one line, at most 80 characters. highlights: at most 3 items of {tone, text}, tone one of good/watch/bad, text at most 60 characters. proposals: {title, body, status, date}, title at most 30 and body at most 80 characters, status one of proposed/done. breakdown.campaigns / breakdown.adgroups: rows of {name, spend, mcpa, target_cpa, state, note} — the three figures are raw numbers, state is one of target_met/improving/watch/worsening/no_data, note at most 40 characters. stated_values: {label, value}, label at most 24 characters and value a raw number or a string of at most 12 characters — a sentence there is refused, because it lands in a numeric column. Do NOT write the KPI funnel or the daily chart: mureo computes both from the stored totals.

    **Over a bound the write is REFUSED — shorten and rewrite, do not re-send the same sentence trimmed.** Lead with the point and drop the connectives; a noun phrase is fine (`CPA 12% over target`, not `The CPA is currently running about 12% above the target we agreed`). Trimming one character at a time and calling again spends the run's context on a bound one rewrite would have met.

    **You may not be the first writer today.** ``display`` is REPLACED WHOLE and the last writer wins — there is no merge. Before you write it, read the current one (``mureo_state_get``). Of what another skill wrote TODAY, carry exactly one thing into your own write: its ``proposals`` that are still live — not yet done, and not contradicted by what you just found. Everything else you write from your own run alone, because a screen assembled from two runs shows a moment that never happened. And carry over NOTHING ELSE: never copy another skill's ``nav_message``, ``highlights``, ``breakdown`` or ``stated_values``, which would put its judgement under your name when you cannot vouch for it. Name yourself in ``source`` so the screen says whose answer it is.

    This is best-effort: if `mureo_state_display_set` is unavailable (e.g. a pure file-mode host without the context MCP), skip it silently — the rest of this skill still works.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.