negative-keyword-sweep

SkillSearch

Find wasted spend in Google Ads search terms and add negatives at the right scope (account list, campaign, or ad group). Use when the user says "find junk keywords", "stop wasting on bad searches", or wants a search-terms cleanup.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the negative-keyword-sweep skill

What this skill tells your AI

The instructions your AI receives, as published by markifact/markifact-mcp in plugins/hermes/markifact/skills/negative-keyword-sweep/SKILL.md and read by ahel’s review.

Goal

Pull the search-terms report, identify spend that isn't producing, group it intelligently, and add negatives at the right level — without nuking legitimate intent.

Inputs to confirm (batch)

  1. Account name (substring) — required.
  2. Scope — single campaign, list of campaigns, or "all Search campaigns" — required.
  3. Lookback — default 30 days; user can override (60 / 90).
  4. Waste thresholds — defaults; allow override:
    • No-converter: spend ≥ $X over lookback AND 0 conversions. Default $X = 2× target CPA, or $50 if no target.
    • Low-CVR offender: spend ≥ 3× target CPA AND CVR < 25% of account CVR.
    • Off-brand / off-intent: terms that match a denylist of words (e.g. free, jobs, careers, tutorial, review, salary, download, cheap, pirate, crack) — calibrated to vertical.
  5. Negative scope preference — default behavior:
    • Recurring junk seen across multiple campaigns → account-level shared negative list.
    • Waste seen only in one campaign → campaign-level.
    • Single-theme misintent (e.g. "free" only hurts a paid product ad group) → ad-group-level.
    • Default match type: phrase, except for single-word obvious junk → broad isn't an option (negatives have no broad), so use exact for single tokens like free.

Workflow

  1. Discover ops: gads_select_accounts, gads_list_report_fields, gads_get_report, gads_create_negative_keyword_list, gads_attach_negative_list_to_campaigns, gads_add_keywords_to_negative_list, gads_add_negative_keywords_to_campaigns, gads_add_negative_keywords_to_ad_groups.
  2. Inspect every op via get_operation_inputs.
  3. Resolve account (substring).
  4. Pull search-terms report via gads_get_report at the search-term granularity (search_term_view resource). Required fields: search_term, campaign_id/name, ad_group_id/name, impressions, clicks, cost, conversions, conv_value, ctr, cvr.
  5. Compute baselines: account-level CVR over lookback. Compute target CPA if not given (use account history / average across the scope).
  6. Apply thresholds → candidate list. Tag each candidate with the trigger reason (no-conv, low-CVR, denylist).
  7. Cluster candidates by intent. Group on shared roots / themes (e.g. free trial spreadsheet, free template excel, free download → cluster "free*"). For each cluster, propose:
    • Negative keyword(s) (the minimum tokens needed to block the cluster — usually a phrase).
    • Match type (phrase by default; exact for single-token denylist hits; phrase for multi-word patterns).
    • Scope (account / campaign / ad-group) per the rules above.
  8. Build a preview table showing every proposed negative with: keyword, match, scope, target campaigns/ad groups, wasted spend that would have been blocked, and a sample of 1–3 search terms it covers. Wait for user confirmation.
  9. Execute:
    • Account-level batch → if a list with the right purpose already exists, use gads_add_keywords_to_negative_list. If not, gads_create_negative_keyword_list then gads_add_keywords_to_negative_list then gads_attach_negative_list_to_campaigns.
    • Campaign-level batch → gads_add_negative_keywords_to_campaigns.
    • Ad-group-level batch → gads_add_negative_keywords_to_ad_groups.
  10. Confirm: count by scope, total estimated spend blocked.

Use safe-write-operations for steps 9 onward.

Platform-specific rules the model MUST respect

  • Negatives have no broad match. Match types are EXACT, PHRASE, BROAD — but Google's docs note that broad-match negatives only block the exact words and their close variants. The mental model: prefer phrase for clusters, exact for single-word junk.
  • Phrase negative blocks any query containing the phrase in order, ignoring close variants on individual words. Don't add running shoes as exact if you mean to block cheap running shoes, red running shoes, etc.
  • Don't over-negate. Negating a single broad term like software from a software seller will block legitimate intent. Always look at the cluster's full search-term sample before adding.
  • Account-level shared lists scale better. Use them for evergreen junk (jobs, careers, download, crack). Don't pollute campaigns with the same negative repeated 10 times.
  • Don't add negatives that conflict with active keywords. If the ad group bids on free trial, don't add free as a negative — Google will silently stop serving it.
  • PMax has its own negative-keyword surface (account-level only, requires support request historically; check current op availability). Search negatives don't apply to PMax automatically.
  • Branded terms: never negate the brand. Always exclude brand variants from candidate clusters.
  • Symmetric vs asymmetric search terms: if a term has converted at all in lookback (≥ 1 conv), drop it from candidates regardless of CVR thresholds.

Failure modes & recoveries

  • Search-terms report empty or near-empty → campaign too new, lookback too short, or campaign isn't Search/PMax. Tell user.
  • "Negative keyword conflicts with positive keyword" rejection → list the conflict and ask user to choose.
  • Cluster has both winners and losers (some terms convert, some don't) → don't add a phrase negative; recommend tighter ad group structure or single-keyword exact negatives instead.

Output to user

Preview:

NegativeMatchScopeTargetWasted spend blockedSample terms
freeEXACTaccount list "Junk - Free"all Search$312free crm, free tool
jobsPHRASEaccount list "Junk - Jobs"all Search$147crm jobs, software jobs
...

After execute:

ScopeNegatives addedSpend that would have been blocked (last 30d)
Account list$
Campaign$
Ad group$

End with: "Re-check search terms in 30 days — patterns shift as keywords mature."

Signals

GitHub stars
48
Forks
8
Last commit
Sep 2026
Advanced
Catalog kind
skill
Key
negative-keyword-sweep
Source
github.com/markifact/markifact-mcp