agentleFS
fourteenwmfourteenwm/ppc-ai-skills/non-serving-keyword-scanner/SKILL.md

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

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
  1. Distinctive:

    Run with no usable account source and the script prints the mode list above instead of a traceback.

    line 62
  2. Distinctive:

    Keep + Investigate, never Pause.

    line 92
  3. Very specific:

    When asked *why* a row is (or isn't) on the list, answer from references/scan-contract.md — exact selection criteria, not paraphrase.

    line 104
  4. Very specific:

    No non-Search coverage: Search campaigns only; PMax/Display/Video don't use this keyword model.

    line 127
  5. Distinctive:

    Read-only against Google Ads — the scan runs GAQL SELECT queries exclusively and never mutates an account.

    line 175

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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_ID

Required: --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's login_customer_id
  • --accounts PATH — curated markdown list; a starter template ships as accounts.example.md with the format documented inside it — copy to accounts.md and 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.yaml location (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:

  1. Present the summary — accounts scanned, accounts with findings, total flagged keywords, and any failed accounts (an inline API Error next to a zero means unscanned, not clean — see references/scan-contract.md).
  2. Read rules.md before 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.
  3. Give every surviving row one of the three verdicts — Pause / Keep / Investigate, defined in rules.md. Ambiguous rows default to Keep + Investigate, never Pause.
  4. Route what needs routing:
    • Dense clusters (whole account or campaign flagged) → account-diagnostic for 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 the sqr-pipeline review gate
    • Pause verdicts → a human executes them in the Google Ads UI or Editor
  5. When asked why a row is (or isn't) on the list, answer from references/scan-contract.md — exact selection criteria, not paraphrase. examples.md shows 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-safety approval 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

FilePurpose
SKILL.mdThis file — how to run + what the operator does with the tab
README.mdZero-context setup guide: install, prerequisites, first run
rules.mdTriage decision logic — read after every run, before any verdict
examples.mdWorked reads: routine triage, an account-level outage, a false recovery
references/scan-contract.mdExact selection criteria, output columns, failure behavior
accounts.example.mdCopyable starter for the curated account-list mode
scripts/non_serving_keyword_scan.pyThe 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.yaml at 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 the spreadsheets + drive.readonly scopes, 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).

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.