non-serving-keyword-scanner
Skill72 starsChanged 4 months agoMITView on GitHub
Scans your accounts for enabled Search keywords with zero impressions over a window (default 180 days) and writes one review tab to a Google Sheet — every row then gets a Pause/Keep/Investigate verdict per rules.md. Report-only, never pauses. Auto-invoke when user says 'scan non-serving keywords', 'find dead keywords', 'keyword cleanup report', or 'non-serving keyword scan'.
- Installs packages
In this repository41 other files it gives its agents.
- account-diagnosticSkill
- ad-copy-generation-frameworkSkill
- ad-copy-verification-standardSkill
- add-account-negative-keywordsSkill
- ads-checkerSkill
- budget-guardianSkill
- budget-recommendation-calculatorSkill
- change-history-checkerSkill
- client-communication-standardsSkill
- competitor-analysis-v2Skill
- conversion-tracking-healthSkill
- dgen-automation-disableSkill
- fair-housing-complianceSkill
- ga4-campaign-cross-referenceSkill
- ga4-cross-analysisSkill
- ga4-lead-quality-investigationSkill
- gaql-query-patternsSkill
- geo-conflict-analyzerSkill
- google-ads-api-setupSkill
- google-ads-querySkill
- google-ads-samplesSkill
- impression-share-diagnosticsSkill
- investigation-methodologySkill
- lead-quality-pattern-analysisSkill
- lead-quality-recommendation-prioritizationSkill
- markdown-to-sheets-presenterSkill
- mcc-hack-auditSkill
- mutation-safetySkill
- neg-conflict-finderSkill
- pmax-asset-automationSkill
- pmax-builderSkill
- portfolio-health-prioritizationSkill
- portfolio-pacing-rulesSkill
- rsa-bulk-editSkill
- rsa-refreshSkill
- rsa-single-accountSkill
- shared-budget-updaterSkill
- sqr-classifierSkill
- sqr-pipelineSkill
- underspending-investigationSkill
- youtube-placement-auditSkill
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 3 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 62
Run with no usable account source and the script prints the mode list above instead of a traceback.
- Distinctive: line 92
Keep + Investigate, never Pause.
- Very specific: line 104
When asked *why* a row is (or isn't) on the list, answer from references/scan-contract.md — exact selection criteria, not paraphrase.
- Very specific: line 127
No non-Search coverage: Search campaigns only; PMax/Display/Video don't use this keyword model.
- Distinctive: line 175
Read-only against Google Ads — the scan runs GAQL SELECT queries exclusively and never mutates an account.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Non-Serving Keyword Scanner
Finds the keywords nobody knows exist: enabled, eligible, and silent — zero impressions across the whole scan window. Across a large portfolio these accumulate into hundreds of rows of clutter and false coverage ("we're covering that term" — except it never serves). One run scans every account you point it at and produces a single review tab.
One operator owns the workflow end to end: run the scan → triage the tab
(rules.md — clusters before rows) → give every row a Pause / Keep /
Investigate verdict → route what needs routing. The scanner itself never
pauses anything; it is a hygiene instrument, not a savings instrument —
every flagged keyword spent $0.00 by definition.
Invocation Triggers
Auto-invoke when user says:
- "Scan non-serving keywords"
- "Find dead keywords"
- "Keyword cleanup report"
- "Non-serving keyword scan"
How to Run
# Scan a single account — fastest first run
python scripts/non_serving_keyword_scan.py --cid 1234567890 --sheet-id YOUR_SHEET_ID
# Scan multiple accounts
python scripts/non_serving_keyword_scan.py --cids "1234567890,2345678901" --sheet-id YOUR_SHEET_ID
# Scan all enabled accounts under your MCC (walks customer_client)
python scripts/non_serving_keyword_scan.py --all --sheet-id YOUR_SHEET_ID
# Scan a curated account list (copy accounts.example.md to accounts.md and edit)
python scripts/non_serving_keyword_scan.py --accounts accounts.md --sheet-id YOUR_SHEET_IDRequired: --sheet-id (any Google Sheet you own — the ID between /d/
and /edit in its URL).
Account selection — three modes, mutually exclusive (no account flag =
the script defaults to --accounts accounts.md):
--cid CID/--cids CID1,CID2,...— explicit accounts, digits only--all— every enabled account under your MCC'slogin_customer_id--accounts PATH— curated markdown list; a starter template ships asaccounts.example.mdwith the format documented inside it — copy toaccounts.mdand edit
Run with no usable account source and the script prints the mode list above instead of a traceback.
Optional flags:
--days N— zero-impression window (default: 180)--tab-name NAME— sheet tab (default:"Non-Serving Keywords"); use dated names for run-over-run history, since the tab is rewritten each run--config PATH—google-ads.yamllocation (default:./google-ads.yaml)
Runtime: ~3–5 seconds per account; plan ~3–5 minutes for a ~50-account MCC.
After the run — operator duties
The script writing the tab is the midpoint of this skill, not the end. On every run:
- Present the summary — accounts scanned, accounts with findings, total
flagged keywords, and any failed accounts (an inline
API Errornext to a zero means unscanned, not clean — seereferences/scan-contract.md). - Read
rules.mdbefore any verdict — triage clusters before rows (a whole account flagged is an outage, not keyword rot), and rule out the false signals: re-enabled or newly launched campaigns, keywords added mid-window, seasonal terms, low-search-volume suppression. - Give every surviving row one of the three verdicts — Pause / Keep /
Investigate, defined in
rules.md. Ambiguous rows default to Keep + Investigate, never Pause. - Route what needs routing:
- Dense clusters (whole account or campaign flagged) →
account-diagnosticfor the upstream diagnosis; auction-side campaign questions →impression-share-diagnostics - A keyword that should serve but doesn't → suspect a blocking
negative first:
neg-conflict-finder - A query being absorbed by a broader keyword elsewhere → confirm with
an ad-hoc pull (
google-ads-query), steer with negatives via thesqr-pipelinereview gate - Pause verdicts → a human executes them in the Google Ads UI or Editor
- Dense clusters (whole account or campaign flagged) →
- When asked why a row is (or isn't) on the list, answer from
references/scan-contract.md— exact selection criteria, not paraphrase.examples.mdshows the expected shape of a full triage read.
What this skill deliberately does NOT do
- No pausing, ever. Report-only by design — the human review step IS the
product. Pause execution happens in the UI/Editor; if pausing ever gets
scripted, that script is a separate tool behind the
mutation-safetyapproval flow. - No savings claims. Flagged keywords have zero impressions, therefore zero clicks and zero spend. The payoff is structural hygiene — never present the tab as recoverable budget.
- No root-cause diagnosis. The scan says which keywords never served,
not why. Why routes per
rules.md:account-diagnostic,neg-conflict-finder,impression-share-diagnostics. - No archaeology. Current-state review only — keywords, ad groups, and
campaigns that are already paused are invisible to it (which is also why
a row vanishing between runs is not proof of recovery —
rules.md). - No non-Search coverage. Search campaigns only; PMax/Display/Video don't use this keyword model. An account with no Search campaigns legitimately returns zero rows.
Files in this skill
| File | Purpose |
SKILL.md | This file — how to run + what the operator does with the tab |
README.md | Zero-context setup guide: install, prerequisites, first run |
rules.md | Triage decision logic — read after every run, before any verdict |
examples.md | Worked reads: routine triage, an account-level outage, a false recovery |
references/scan-contract.md | Exact selection criteria, output columns, failure behavior |
accounts.example.md | Copyable starter for the curated account-list mode |
scripts/non_serving_keyword_scan.py | The scan engine (read-only GAQL + one sheet write) |
diagrams/ | Workflow diagrams used by the README |
Output
One review tab in the sheet you passed (--tab-name, default
"Non-Serving Keywords"), stamped with the run time — created if missing,
cleared and rewritten on every run with findings, and left untouched on
a clean run (a stale tab keeps its old stamp — check it). Columns and exact
write behavior: references/scan-contract.md.
Prerequisites
- Credentials:
google-ads.yamlat project root — see the google-ads-api-setup skill if you don't have one. The sheet-writing step reuses this same file's OAuth credentials for the Sheets API — its refresh token must carry thespreadsheets+drive.readonlyscopes, which the setup skill's generator grants by default (token predates that? re-run the generator once — a 403 on the write step is this) - Output sheet: any Google Sheet you own — you pass its ID via
--sheet-id - Python packages:
pip install google-ads gspread google-auth pyyaml
Safety
Read-only against Google Ads — the scan runs GAQL SELECT queries exclusively
and never mutates an account. Its only write is the review tab, into a sheet
you own. Acting on the findings is a separate, human-executed step (see the
boundary above and mutation-safety).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

