What it does
Most ads syncs are upsert-only: they write what the API returns and never touch what it doesn't. But when a campaign or ad set is archived or deleted on Meta, it simply stops appearing in default API listings. Nothing comes back to say this is gone, so the local row keeps its last status forever.
The result is a zombie: your database, spreadsheet, or dashboard says ACTIVE while Meta archived the thing weeks ago. In production we caught an ad set that read ACTIVE locally for 11 days after it was archived on Meta; every report and every automated decision built on that row was wrong for 11 days. This skill finds your zombies.
Quick start
git clone https://github.com/PierreNicolasMarquez/pierce-skills.git
cp -r pierce-skills/skills/meta-zombie-audit ~/.claude/skills/Then in Claude Code: "run the meta-zombie-audit skill against my campaigns table".
The full SKILL.md, with the exact API calls, lives in the pierce-skills repo. MIT-licensed, runs on your token, never phones home.
How it works
You bring your local list (a synced table, a tracking spreadsheet, a board) with object id, name and believed status, plus a Meta token with ads_read.
- 1
Pull truth with archived objects included
Default listings hide the dead, so the skill asks for everything explicitly with an effective_status filter covering ACTIVE, PAUSED, ARCHIVED, CAMPAIGN_PAUSED and WITH_ISSUES (plus ADSET_PAUSED for ad sets), paginating to the end. Notes from running this for real: DELETED is not accepted in the listing filter, the filter value is JSON and must be URL-encoded, and any error key in a response is a hard stop, never an empty result.
- 2
Direct-fetch anything still missing
Any id in the local list absent from step 1 gets an individual lookup. An error on that fetch (object missing or a permission error on a deleted object) means DELETED server-side.
- 3
Diff every local row
ZOMBIE: local says ACTIVE, Meta says ARCHIVED, DELETED, or not found. The dangerous class, reported first, with updated_time when available so you know how long the row has been lying. STATUS DRIFT: both objects exist but statuses differ, including an ACTIVE ad set under a paused campaign reading ADSET_PAUSED effective. ORPHAN: exists on Meta, absent locally. CLEAN: statuses agree.
- 4
Report blast radius
One table sorted zombies first: id, name, local status, Meta effective_status, last Meta update. Then one sentence per zombie on blast radius: what reports or automations read this row?
- 5
Fix the class, not the instance
Repairing the rows fixes today. The pattern fix is sync-side expiry (after each sync, any locally-ACTIVE row not present in the latest API response gets a missing_since flag and is demoted after a grace period) or a read-side staleness filter (every consumer of ACTIVE also requires recent spend or impressions before trusting the status). A synced database wants the first, a spreadsheet the second.
Caveats
Deleted objects are only caught by the direct fetch in step 2; the listing filter cannot return them.
An empty API response and an API error are different things. Treat any error key as a hard stop or the audit will certify a broken pipe as clean.
Related skills
Meta anomaly check
Daily Meta anomaly check against a trailing 14-day baseline. Silent on quiet days; one funded recommendation when something breaks.
Read the method →Ads APIMeta lead-form preflight
Preflight checks for pushing Instant Form lead ads via API, including the misleading error 1892181 whose real cause is usually a missing token scope.
Read the method →