Google audit counts ad-group negative keywords as enabled keywords, producing false "self-blocked keywords" and "keyword bloat" findings

Author: reikjarloeklCreated Sep 16, 2026Updated Sep 16, 2026

Summary

The /ads google audit reported that 133 enabled keywords "cannot serve" because they are blocked by the account's own negatives, and that 185 of 261 enabled keywords had zero impressions in 90 days. Both findings are artefacts of the keyword inventory including ad-group negative keywords (ad_group_criterion.negative = TRUE) as if they were positive keywords. The account has 129 enabled positive keywords, zero of them mirrored by a negative in the same ad group, and one keyword text with a genuine (and intended) negative conflict.

Environment

  • claude-ads at ac21644 (2026-09-11), installed with install.sh into Claude Code, run headless with claude -p "Run /ads google." and the official google-ads-mcp server.
  • Account under audit: three enabled Search campaigns, seven enabled ad groups, nine shared negative lists attached to one campaign. Details available privately on request.

What the report said

Finding H-4 (severity high, "confidence: high"): "133 enabled keywords cannot serve — blocked by the account's own negatives in the same ad group/campaign. 134 matched, 133 with zero impressions in 90 days. Cross-check of 261 live enabled keywords against 209 ad-group and 168 campaign negatives." The evidence section lists the "self-blocked" terms per ad group (93, 30, 5, 5, 1) and states that each "exists simultaneously as an enabled positive keyword and as a phrase negative in the same ad group", giving single-word examples such as pdf, checkliste, training, auditor. Recommendation 8 in the action plan: "export and remove the inert positives".

Finding M-7: "185 of 261 enabled keywords in live ad groups (70.9%) had zero impressions in 90 days." Finding M-6 also states one ad group "holds 130 enabled keywords".

What the account actually contains

Pulled directly from the Google Ads API (v30 client) with ad_group_criterion.type = 'KEYWORD' AND ad_group_criterion.status = 'ENABLED' AND ad_group.status = 'ENABLED' AND campaign.status = 'ENABLED':

Set Count
Positive keywords (negative = FALSE) 129
Ad-group negative keywords (negative = TRUE) 132
Positives with zero impressions in 90 days (keyword_view) 53
Ad-group negatives whose text is also a positive in the same ad group 0
Genuine conflicts (positive blocked by an in-scope negative, match-type aware) 2 rows, one keyword text

The arithmetic lines up exactly with the report: 129 positives + 132 negatives = 261 "enabled keywords"; 132 negatives (which never receive impressions) + 53 real zero-impression positives = 185. The per-ad-group "self-blocked" counts (93, 30, 5, 5, 1) are the sizes of those ad groups' negative lists, and the example words in the report exist in the account only as negatives. The ad group described as holding 130 keywords has 39 positives and 91 negatives.

Why it happens

Nothing in the skill instructs the model to filter on ad_group_criterion.negative. ads/references/google-audit.md lists G-KW1 ("Zero-impression keywords") and G-KW2 only as "Conditional evidence control: establish applicability and evaluate from current account evidence", and the G-KW1 entry in control-plane/manifests/control-registry.json carries no evidence recipe. A natural inventory query against ad_group_criterion with status = 'ENABLED' therefore returns negatives alongside positives, and the "self-blocked" cross-check matches every ad-group negative against itself. The exact GAQL the run used is not written to the log, so this is inferred from the counts, but the counts leave no other explanation.

Impact

The report presents a high-severity, high-confidence finding whose recommended fix ("remove the 133 inert positives") would delete the account's ad-group negative keywords. The same inventory error inflates M-6 and M-7, so three findings and one action-plan item are wrong on an otherwise careful report.

Suggested fix

  1. Give G-KW1, G-KW2 and the negative-conflict check an explicit evidence recipe: keyword inventory from ad_group_criterion must include ad_group_criterion.type = 'KEYWORD' AND ad_group_criterion.negative = FALSE; negatives come from the same resource with negative = TRUE, from campaign_criterion, and from shared_criterion joined through campaign_shared_set.
  2. Make the conflict check match-type aware and scoped: a broad negative blocks a positive when all its words appear, a phrase negative when its words appear contiguously in order, an exact negative only on identical text, and only negatives from the same ad group, the same campaign, or a shared list attached to that campaign apply.
  3. Add a plausibility guard: if the number of "self-blocked" keywords equals the number of ad-group negatives in scope, or if the inventory count exceeds the keyword_view row count (which never contains negatives), the finding should be downgraded to unknown rather than reported as high confidence.

Minimal inventory query that avoids the problem:

SELECT campaign.id, ad_group.id, ad_group_criterion.criterion_id,
       ad_group_criterion.keyword.text, ad_group_criterion.keyword.match_type
FROM ad_group_criterion
WHERE ad_group_criterion.type = 'KEYWORD'
  AND ad_group_criterion.negative = FALSE
  AND ad_group_criterion.status = 'ENABLED'
  AND ad_group.status = 'ENABLED'
  AND campaign.status = 'ENABLED'

Happy to share the verification script or an anonymised extract of the report if that helps.