Tracking Agent docs
How the Tracking Agent audit works
The Tracking Agent audit compares your tracking plan, what your code fires, and what your analytics tool actually received. Every event and user property lands in exactly one bucket, such as healthy, broken, dead, or arriving but not in the plan, and the report gives a health score. Run it with datalyze audit and a data snapshot from your analytics tool.
What can a Tracking Agent audit cover?
| Scope | Option | Typical use |
|---|---|---|
| Everything | No option | At install, then monthly |
| Some events or user properties | --events 'checkout_*,plan_tier' | A suspected problem |
| One area | --area posts (an area file) or --area app/checkout/ (code under a path) | Before or after a big release |
| Recent changes | --since 2026-09-01 | A quarterly review |
| One pull request | --pr 142 | Checking a merged change |
How do I run a Tracking Agent audit?
- Build a data snapshot with
datalyze snapshot mixpanel,posthogoramplitude. The Mixpanel recipe reads Mixpanel's query APIs and a sample from the raw event export. For each tool's credentials, and for other tools, see connecting analytics. - Run
datalyze audit --snapshot snapshot.json, with a scope option if you want one, and--md report.mdto save the report. - Read the report, starting with broken and dead items.
- Optionally run it again with
--applyto write the statuses the data proves, plus_audit.jsonfor the Google Sheet's Audit tab.
datalyze snapshot mixpanel --project-id 1234567 --region us --out snapshot.json
datalyze audit --snapshot snapshot.json --md report.mdThe install audit runs on its own, on the setup pull request, with the recipe for analytics.tool. After a merge, the post-release watch runs a PR-scoped audit on its own (below). Running a full audit on a schedule or from the review page isn't available yet.
What do the audit buckets mean?
| Bucket | Plan, code and data | What to do |
|---|---|---|
| Broken | In the plan and the code, nothing arriving | Find why the call doesn't send: a dead code path, a flag, a blocked client |
| Deprecated but still arriving | Deprecated in the plan, still arriving, outside any dual-fire window | Remove the remaining call |
| Arriving, with problems | Arriving, with property, volume, duplicate or funnel problems | Read the checks listed against it |
| Dead | Live in the plan, gone from the code, nothing arriving | Deprecate it |
| Arriving, not in the plan | In the code and arriving, missing from the plan; most are renames | Add it to the plan, or rename the call |
| Arriving, not in the plan or this codebase | Arriving from somewhere this repo doesn't know about | Find the sender: another service, an app, a tag manager |
| Arriving from elsewhere | In the plan and arriving, but not sent from this codebase | Mark it external: true if another service sends it |
| Planned, already arriving | Still planned in the plan, but arriving | --apply sets it to implemented |
| Deprecated, call still in code | Deprecated, nothing arriving, but the call remains | Delete the call |
| Planned, not built yet | Planned, not in the code, nothing arriving | Nothing, until it ships |
| In code, not in the plan, nothing arriving | A call nobody planned that sends nothing | Add it to the plan or delete it |
| Not measured | The snapshot has no data for it | Collect it, or ignore; it's left out of the health score |
| Deprecated and gone | Deprecated, no call, nothing arriving | Nothing: history kept |
| Healthy | In the plan and the code, arriving with the right properties | Nothing |
What else does the audit check on events that arrive?
- Properties: a planned property that never arrives, one filled on less than 95% of events (
audit.min_fill_rate), one arriving as two types, or values outside the plan. - Volume: an event that stopped arriving, or whose daily volume over the last 7 days fell below half of what it was before.
- Funnels: more outcomes than intents, such as more
order_placedthancheckout_started. - Double-firing: more than 5% of an event's rows repeating for the same user within a second (
audit.max_duplicate_rate). - Personal data: property values shaped like an email or phone number.
- User properties: fill rate, stale values, and
setused whereset_oncewas meant. - Identity: an identified share below 20% (
audit.min_identified_share) and duplicate profiles, where the tool reports them. - Super properties that don't arrive: an event whose properties are all known but lack a super property declared for its side, which usually means a path that skips the wrapper adding it, such as a new typed tracker call next to a legacy wrapper.
How do I read the Tracking Agent audit report?
The health score is the share of live plan items (implemented or needs_review) that are healthy, counting only items the snapshot measured. Below it, findings are grouped by bucket, each with its code locations and the query behind every figure, so you can check any number yourself.
The audit never adds or deletes registry entries. --apply only changes statuses the data proves: planned and arriving becomes implemented, and implemented with nothing arriving becomes needs_review.
When we ran the install audit on this site, the registry came out at 46 events in 6 area files, with no drift between the plan and the code, and the baseline accepted 41 legacy findings.
What does the post-release watch check after a merge?
datalyze watch checks that the tracking a merged pull request changed arrives as planned. The workflow runs it every day at 06:41 UTC; to run it by hand, open Actions, then Datalyze, then Run workflow, and pick watch.
- It finds recent changes: pull requests merged into your default branch in the last 14 days that have a proposal file, and in each, the items a reviewer approved (events and user properties). A property item is watched through its event.
- It waits for traffic: until at least 50 of each watched event have arrived, counted from the merge day, or 48 hours after the merge, whichever comes first. Deprecations and user properties wait the full 48 hours.
- It runs a PR-scoped audit against a fresh snapshot from the recipe for
analytics.tool, or the file given with--snapshot. - It reports once: one comment on the merged pull request, final once posted, with each item's result, such as arrives as planned, a deprecated event still arriving, values outside the plan, or a property not arriving. The same results become rows on the Sheet's Audit tab with Source "Watch PR #n", where a person marks each one verified.
- It opens fix pull requests for the problems, one per area, through the PR check, so both human gates apply.
# .datalyze/config.yaml
watch:
min_events: 50
hours: 48
lookback_days: 14
open_fix_prs: true # false: report onlyWithout data, because no recipe or credentials are set, the watch waits, then says on the PR that it couldn't run yet and why. It tries again on each run while the PR is inside watch.lookback_days, so it reports once the credentials are set; on its last run inside that window it posts that it stopped. A snapshot that fails on a run, for example on a rate limit, counts as no data for that run. For a mobile app the watch waits for the app version that ships the change and counts only that version and newer ones: see mobile apps.
How do audit findings get fixed?
Through the PR check, like any other change, so both human gates apply: a person approves the proposal and signs off the result. You choose which findings to fix, and the agent opens one fix pull request per area.
- In the Google Sheet: put
yes(or ✓ or x) in the Fix column of Audit tab rows. The hourly requests job opens one fix pull request per area for the ticked rows and writes its link into Fix PR. Healthy rows and rows that already have a Fix PR are skipped. - From a laptop: run
datalyze fix --items <name,name>. It reads buckets and checks from--audit(defaultanalytics/registry/_audit.json), and--sourcerecords where the findings came from, such as"Watch PR #12". - From the watch: problems it finds become fix pull requests on their own, unless
watch.open_fix_prsisfalse.
A fix pull request is a proposal pull request, like one from the Requests tab: the request agent proposes the items, code checks them, the proposal is signed, and the PR check adopts it only with that signature. Then a reviewer approves and signs off as usual. The playbook chapter on data accuracy as a system covers keeping numbers right between audits.
Questions about the audit
Can we audit without connecting our analytics tool?
datalyze audit needs a data snapshot. Without one, datalyze scan still compares the plan with the code and reports drift, dead calls and SDK calls outside the adapter.
Does the audit change our registry?
Only with --apply, and only statuses: it never adds, deletes or rewrites an entry.
How is "dead" different from "arriving from elsewhere"?
Dead means the call is gone and nothing arrives. Arriving from elsewhere means the call isn't in this codebase but data still comes in, usually from another service or platform, so it isn't dead.
Should we run the audit ourselves or hire someone?
The audit finds where the plan, code and data disagree. Our analytics audit is a person doing that and more: whether you track the right things for the questions your team asks.
How soon after a merge does the watch report?
Once at least 50 of each watched event have arrived since the merge day, or 48 hours after the merge, whichever is first. Deprecations and user properties always wait the full 48 hours.