By the end of this lesson you will have a gate sitting between Claude Code and your site: a rehearsal by default, a JSONL audit log on every run, one write per second, a backup before each change, and an approval that covers one plan rather than one run.
The arithmetic comes before the workflows, because it is the part every published account leaves out: the savings in circulation are gross, and netting them off changes which jobs are worth handing to an agent.
One boundary was drawn already in the scope lesson of this level: Claude analyses crawl data, it does not crawl sites (Lawrence Hitches, 2 April 2026). Claude Code is where that becomes practical, because a terminal agent works on files: a crawl export, your logs, a Search Console response, a repository template. Something else produces them.
Everything here treats the engines as equipment: what you point them at, and how you check what comes back. Being quoted inside an AI answer is a separate question on separate evidence, worked in Level 4 on GEO and AIO; whether the two are one discipline or two is argued in the trade and not settled here. The level index carries the rest.
What you’ll learn
- Net a published time saving down to what it is worth at your own task count, and find the run count at which an MCP server pays for itself.
- Read the only survey here that publishes a sample size, without conflating its three similar-looking percentages.
- Ship a five-guarantee dry-run gate: rehearsal by default, JSONL audit log, one write per second, pre-write backup, approval bound to a plan signature.
- Separate the jobs whose output validates itself from the ones where the only check is a person.
- Give every failure its own exit code, so a run that wrote nothing fails loudly instead of reporting success.
The arithmetic nobody publishes, and the one survey with a sample size
The time savings published for Claude Code and SEO are gross, and the net is a different number. Lawrence Hitches’s account on the StudioHawk blog, 12 May 2026, gives two: a metadata rewrite through a CMS API with Search Console data falling from six to eight hours to minutes, and an internal linking project from three to five days to under an hour. Both time the machine. Neither deducts the server setup, the tokens spent loading tool definitions, the subscription, or the human verification the same article calls mandatory, and no sample size is stated.
The subtraction:
net_saving = (T_manual - T_agent - T_verify) * N - T_setup - C_period
break_even: N > T_setup / (T_manual - T_agent - T_verify)
no solution when T_verify >= T_manual - T_agent
T_setup is installing and authorising the servers once, T_verify checking one run, N the runs in the period judged, C_period the subscription and tokens across it. A yearly task rarely repays a server; a weekly one usually does.
The third line is the one worth pinning up. When verification takes as long as the manual work minus the agent’s time, the inequality has no solution at any value of N, because every repetition brings its own review: the task moves from doing to checking rather than going away.
| Cost line | Published with the savings? | What is known, with a source |
|---|---|---|
| Task time, before and after | Yes | 6–8 hours to minutes; 3–5 days to under an hour (StudioHawk, 12 May 2026) |
Setup, T_setup |
No | No published measurement locatable |
| Tool-definition tokens | No | The size of the problem only: the Ahrefs connector exposed 61 tools on 20 September 2026 |
Subscription, C_period |
No | Semrush’s MCP documentation, 5 August 2026: 50,000 API units, no MCP fee on an eligible plan |
Review, T_verify |
No | Called mandatory by the same authors; 57% name quality as their reason for holding back (Keyword.com, n=97) |
Only the last row has a figure behind it, from the one survey here that publishes its sample. Keyword.com’s State of AI in SEO 2026, 1 January 2026, reports n=97 usable responses: small and self-selected, so it orders tasks rather than measuring the profession. Full automation is a rarity in it. 1% of respondents call their work fully automated, which is 1 person in 97.
Three other readings off that page matter. Technical SEO audits are a task 38% of respondents put AI on. Of the 79% who said some task was automatable and that they had decided against automating it, 40% named technical SEO. And the answer given most often for holding back, at 57%, is “Quality not good enough”.
Cite that 57% carefully: the same page carries a separate 70% naming poor-quality output, hallucinations or quality-control time as the biggest limitation, and an unrelated 57% for ChatGPT adoption. The figure here is the hold-back reason.
Where Claude Code’s reach stops
Claude Code runs in a terminal and its unit of work is a file: it opens exports, joins them and writes new files out the other side. Lawrence Hitches sets out the jobs in Claude for Technical SEO: Audits, Crawl Fixes & Schema, 2 April 2026: log analysis, JSON-LD generation, hreflang review, and redirect maps with the rules already written out for nginx, Apache, .htaccess or Cloudflare. That is one practitioner writing up his own method and it carries no sample size, worth knowing about the provenance of a task list.
Three things it will not do at all: fetch one of your URLs, run your JavaScript, or turn up a page nothing links to. A crawler goes first, and whatever that crawl missed stays missing from every answer afterwards.
People are looking for this and there is no honest way to say how many. The Google Autocomplete harvest run for this course on 20 September 2026 got eight suggestions from the seed claude code seo, beaten by one other seed. A suggestion appearing at all is the whole of the signal, and nobody with a name on it publishes a volume for these terms.
The table is cut on the question the lesson turns on: not what the agent produces, but what the output can be checked against.
| Job | The file it reads | What the output is checked against | Mechanical? |
|---|---|---|---|
| Log analysis | The raw server log | Which URLs you think deserve budget | No |
| JSON-LD generation | Page HTML or template | The rich results test | Yes |
| Redirect rule generation | An approved mapping | That mapping, row by row | Yes |
hreflang review |
The crawl export | Reciprocity across the declared set | Yes |
| Internal link planning | A crawl with page content | Whether a person finds each pair relevant | No |
| Redirect mapping | Old and new URL lists | Whether each target is the equivalent | No |
A trustworthy export is ordinary technical SEO: an automated pass over a site whose canonical tags are wrong builds the wrong thing faster.
The five guarantees, and the one almost everybody implements wrong
Claude Code holding write access needs five guarantees, and all five belong in your code rather than the model’s good intentions. Hitches names them in the StudioHawk article of 12 May 2026: a DRY_RUN mode, JSONL audit logging, one request per second, backups taken before writing, and a human approval gate ahead of any change.
| Guarantee | What it prevents | How you prove it is there |
|---|---|---|
| Rehearsal by default | An accidental run writing to production | With no flags, no file changes |
| JSONL audit log | Not knowing what happened, or when | One line per event, refusals too |
| One write per second | Flooding your own API or being throttled | Ten writes cannot finish in ten seconds |
| Pre-write backup | Losing the previous state | A .bak per file touched |
| Approval gate | The plan changing after review | The signature matches the approved |
The fifth is the one almost everybody implements wrong. A prompt asking “apply these changes?” approves a run, and between review and run the plan can change: the agent regenerates it, a colleague edits a row, a retry picks up a newer file. So this gate approves a signature of one plan. Change one character and the signature changes, and the write is refused instead of landing on text nobody read.
The rate limit belongs in your code because a speed configured in a tool’s own interface is not necessarily the speed an agent-driven path uses; the two do not always share configuration, and what breaks when an agent drives a crawler is a lesson of its own later in this level. The defensive version is cheap: a one-second pause inside the write loop.
Write the log during rehearsals too. A log that only exists once something has changed is an incident report, not a control.
The gate, its exit codes, and a session that reaches all of them
Original to this lesson: 199 lines of Python, standard library only, no network calls, on local files, so you can run it before it goes near a live site. For a CMS API, replace one function, write().
Exit codes first, because this is where a gate usually fails. A code with two meanings is worse than no code: if a wrong file format and a real finding exit the same way, a scheduled run cannot tell a problem from a result. Each code below carries one meaning, evaluated in the order listed.
| Exit code | The one thing it means |
|---|---|
| 1 | -h or --help: usage printed, nothing read |
| 2 | Wrong argument count, or an unknown flag |
| 3 | The plan path does not exist |
| 4 | The path exists and is not a regular file |
| 5 | A regular file whose bytes could not be read |
| 6 | The plan’s bytes are not UTF-8 text |
| 7 | UTF-8 text, and not valid JSON |
| 8 | Valid JSON, and not a list of objects each carrying id, file, find and replace as strings |
| 9 | A list, and empty: nothing to do |
| 10 | Writing requested, gate refused, nothing written |
| 11 | No refusal, and a change was not applicable |
| 0 | No refusal, every change applicable |
Twelve codes looks excessive until you have to debug a nightly run from one. Codes 3 to 9 are seven things that look identical from outside, all of them “bad plan”, and each wants a different fix, so each gets a code. The read failure is awkward to test and it is reachable: point the gate at /proc/self/mem, a regular file whose bytes cannot be read, and it exits 5. And --help exits 1, not 2, because asking for usage is not a usage error.
#!/usr/bin/env python3
"""Dry-run gate for bulk SEO edits. Writes nothing unless every guarantee is met.
python3 write_gate.py plan.json # dry run, the default
python3 write_gate.py plan.json --apply \
--signature <sig> --approved-by <name> # the only writing form
Standard library only. No network calls. The plan is a JSON list of objects,
each carrying id, file, find and replace.
One cause per exit code, checked in this order, so the first that applies wins:
1 -h or --help was given: usage printed, nothing inspected.
2 Wrong number of arguments, or an argument the parser does not know.
3 The plan path does not exist.
4 The plan path exists and is not a regular file.
5 The plan is a regular file whose bytes could not be read.
6 The plan's bytes are not UTF-8 text.
7 The plan is UTF-8 text and is not valid JSON.
8 The plan is valid JSON and is not a list of objects each carrying id,
file, find and replace as strings.
9 The plan is a list and it is empty: nothing to do.
10 Writing was requested and the approval gate refused it. Nothing written.
11 No refusal, and at least one change in the plan was not applicable.
0 No refusal, and every change in the plan was applicable.
"""
import argparse
import hashlib
import json
import os
import shutil
import sys
import time
from datetime import datetime, timezone
PAUSE_SECONDS = 1.0 # one write per second, enforced here and nowhere else
REQUIRED_KEYS = ("id", "file", "find", "replace")
def stamp():
return datetime.now(timezone.utc).isoformat(timespec="seconds")
def audit(path, **fields):
"""One JSON object per line, appended. Written in dry runs too."""
fields["ts"] = stamp()
with open(path, "a", encoding="utf-8") as handle:
handle.write(json.dumps(fields, ensure_ascii=False, sort_keys=True) + "\n")
def signature(plan):
raw = json.dumps(plan, sort_keys=True, ensure_ascii=False).encode("utf-8")
return hashlib.sha256(raw).hexdigest()[:16]
def fail(code, message):
sys.stderr.write(message + "\n")
raise SystemExit(code)
def load_plan(path):
if not os.path.exists(path):
fail(3, "plan not found: %s" % path)
if not os.path.isfile(path):
fail(4, "not a regular file: %s" % path)
try:
raw = open(path, "rb").read()
except OSError as error:
fail(5, "could not read plan: %s" % error)
try:
text = raw.decode("utf-8")
except UnicodeDecodeError as error:
fail(6, "plan is not UTF-8 text: %s" % error)
try:
plan = json.loads(text)
except ValueError as error:
fail(7, "plan is not valid JSON: %s" % error)
if not isinstance(plan, list):
fail(8, "plan must be a JSON list, found %s" % type(plan).__name__)
for index, change in enumerate(plan):
if not isinstance(change, dict):
fail(8, "change %d is not an object" % index)
for key in REQUIRED_KEYS:
if not isinstance(change.get(key), str):
fail(8, "change %d has no string %r" % (index, key))
if not plan:
fail(9, "plan is an empty list: nothing to do")
return plan
def inspect(change):
"""Return (applicable, reason). Reads the file; never writes."""
path = change["file"]
if not os.path.isfile(path):
return False, "file missing"
try:
text = open(path, encoding="utf-8").read()
except (OSError, UnicodeDecodeError) as error:
return False, "unreadable: %s" % error.__class__.__name__
hits = text.count(change["find"])
if hits == 0:
return False, "pattern absent"
if hits > 1:
return False, "pattern ambiguous (%d matches)" % hits
if change["find"] == change["replace"]:
return False, "no-op"
return True, "applicable"
def write(change, backup_dir):
path = change["file"]
os.makedirs(backup_dir, exist_ok=True)
suffix = stamp().replace(":", "").replace("+0000", "Z")
backup = os.path.join(backup_dir,
os.path.basename(path) + "." + suffix + ".bak")
shutil.copy2(path, backup) # the copy happens before the file is touched
text = open(path, encoding="utf-8").read()
with open(path, "w", encoding="utf-8") as handle:
handle.write(text.replace(change["find"], change["replace"], 1))
return backup
def refusal(args, expected):
"""Why the gate is refusing, or None. Operator omissions before plan drift."""
if not args.approved_by:
return "no approver named"
if not args.signature:
return "no signature supplied"
if args.signature != expected:
return "signature mismatch"
return None
def main():
parser = argparse.ArgumentParser()
parser.add_argument("plan")
parser.add_argument("--apply", action="store_true")
parser.add_argument("--signature", default="")
parser.add_argument("--approved-by", default="")
parser.add_argument("--log", default="audit.jsonl")
parser.add_argument("--backups", default="backups")
try:
args = parser.parse_args()
except SystemExit as stop:
raise SystemExit(1 if stop.code == 0 else 2)
plan = load_plan(args.plan)
expected = signature(plan)
print("plan: %d changes | signature %s" % (len(plan), expected))
reason = refusal(args, expected)
refused = args.apply and reason is not None
mode = "apply" if (args.apply and not refused) else (
"refused" if refused else "dry-run")
audit(args.log, event="start", signature=expected, changes=len(plan),
mode=mode, approved_by=args.approved_by or None)
if refused:
print("WRITE REFUSED: " + reason, file=sys.stderr)
audit(args.log, event="write_refused", reason=reason,
signature_expected=expected, signature_given=args.signature or None,
approved_by=args.approved_by or None)
applicable = blocked = written = 0
for change in plan:
ok, why = inspect(change)
if not ok:
blocked += 1
audit(args.log, event="blocked", id=change["id"],
file=change["file"], reason=why)
print(" [blocked] %s: %s" % (change["id"], why))
continue
applicable += 1
if mode != "apply":
audit(args.log, event="dry_run", id=change["id"],
file=change["file"], reason=why)
print(" [dry-run] %s: applicable" % change["id"])
continue
backup = write(change, args.backups)
written += 1
audit(args.log, event="written", id=change["id"],
file=change["file"], backup=backup)
print(" [written] %s | backup %s" % (change["id"], backup))
time.sleep(PAUSE_SECONDS)
audit(args.log, event="end", applicable=applicable, blocked=blocked,
written=written)
print("applicable: %d | blocked: %d | written: %d | log: %s"
% (applicable, blocked, written, args.log))
if refused:
return 10
if blocked:
return 11
return 0
if __name__ == "__main__":
sys.exit(main())
Three fixtures make the session reproducible; leave public/road-shoes/ absent on purpose.
$ mkdir -p public/trail-shoes public/kids-shoes
$ cat > public/trail-shoes/index.html <<'HTML'
<!doctype html>
<meta name='description' content='Buy trail running shoes online'>
<h1>Trail running shoes</h1>
HTML
$ cat > public/kids-shoes/index.html <<'HTML'
<!doctype html>
<meta name='description' content='Shoes for kids'>
<meta property='og:description' content='Shoes for kids'>
<h1>Kids shoes</h1>
HTML
The plan is a JSON list, one object per change: an identifier, a path, the text to find and its replacement. This one carries four changes, built so three of the five blocking conditions fire.
[
{"id": "meta-001",
"file": "public/trail-shoes/index.html",
"find": "content='Buy trail running shoes online'",
"replace": "content='Trail running shoes: sizing, drop and 14 models tested'"},
{"id": "meta-002",
"file": "public/road-shoes/index.html",
"find": "content='Buy road running shoes online'",
"replace": "content='Road running shoes: 11 models tested on the same route'"},
{"id": "meta-003",
"file": "public/kids-shoes/index.html",
"find": "content='Shoes for kids'",
"replace": "content='Kids running shoes: fit, sizing and 9 models'"},
{"id": "meta-004",
"file": "public/trail-shoes/index.html",
"find": "<h1>Trail running shoes</h1>",
"replace": "<h1>Trail running shoes</h1>"}
]
The session follows, verbatim. The sed plays the part of whatever alters a plan after review.
$ python3 write_gate.py plan.json
plan: 4 changes | signature a43349c1fc983e87
[dry-run] meta-001: applicable
[blocked] meta-002: file missing
[blocked] meta-003: pattern ambiguous (2 matches)
[blocked] meta-004: no-op
applicable: 1 | blocked: 3 | written: 0 | log: audit.jsonl
$ echo $?
11
$ python3 write_gate.py plan.json --apply --signature a43349c1fc983e87
plan: 4 changes | signature a43349c1fc983e87
WRITE REFUSED: no approver named
[dry-run] meta-001: applicable
[blocked] meta-002: file missing
[blocked] meta-003: pattern ambiguous (2 matches)
[blocked] meta-004: no-op
applicable: 1 | blocked: 3 | written: 0 | log: audit.jsonl
$ echo $?
10
$ sed -i 's/14 models/15 models/' plan.json
$ python3 write_gate.py plan.json --apply --signature a43349c1fc983e87 --approved-by txema
plan: 4 changes | signature f1b042cc041eb20f
WRITE REFUSED: signature mismatch
[dry-run] meta-001: applicable
[blocked] meta-002: file missing
[blocked] meta-003: pattern ambiguous (2 matches)
[blocked] meta-004: no-op
applicable: 1 | blocked: 3 | written: 0 | log: audit.jsonl
$ echo $?
10
$ sed -i 's/15 models/14 models/' plan.json
$ python3 write_gate.py plan.json --apply --signature a43349c1fc983e87 --approved-by txema
plan: 4 changes | signature a43349c1fc983e87
[written] meta-001 | backup backups/index.html.2026-09-29T110759Z.bak
[blocked] meta-002: file missing
[blocked] meta-003: pattern ambiguous (2 matches)
[blocked] meta-004: no-op
applicable: 1 | blocked: 3 | written: 1 | log: audit.jsonl
$ echo $?
11
Read the counts. Four changes, one applicable and three blocked: meta-002 points at a file that does not exist, meta-003 matches its pattern twice, and meta-004 replaces text with itself. The script’s two other blocking conditions, an absent pattern and an undecodable file, do not occur here and are not counted as findings.
The last run is the instructive one. The write succeeded and the code is still 11, because three changes remain unapplied. A gate returning 0 there tells a continuous-integration step that a partly executed plan was clean.
Three audit lines: one per refusal, plus the write. A refusal records both signatures, so you can see which moved.
{"approved_by": null, "event": "write_refused", "reason": "no approver named", "signature_expected": "a43349c1fc983e87", "signature_given": "a43349c1fc983e87", "ts": "2026-09-29T11:07:59+00:00"}
{"approved_by": "txema", "event": "write_refused", "reason": "signature mismatch", "signature_expected": "f1b042cc041eb20f", "signature_given": "a43349c1fc983e87", "ts": "2026-09-29T11:07:59+00:00"}
{"backup": "backups/index.html.2026-09-29T110759Z.bak", "event": "written", "file": "public/trail-shoes/index.html", "id": "meta-001", "ts": "2026-09-29T11:07:59+00:00"}
Run this gate often enough to stop thinking about it and it belongs in a skill with its thresholds in a file rather than a command you retype.
Jobs whose output checks itself
Some technical SEO outputs arrive with a check that costs a minute and returns a verdict, and those are the jobs to hand over first. Generated JSON-LD goes through the rich results test, which reports issues or does not. Redirect rules for nginx, Apache, .htaccess and Cloudflare (Hitches, 2 April 2026) are a deterministic translation of a mapping somebody approved, so they can be diffed against it row by row. A hreflang review is arithmetic over a set: every declared pair either points back or it does not.
If you can name the command that returns pass or fail on the output, the job is delegable and the rehearsal is the only ceremony it needs. If you cannot name it, the job belongs in the next section whatever its output looks like.
Mechanical checking has a limit: a rich results test says markup is valid, not that it is true. A schema with the wrong price validates perfectly, so somebody still reads a sample.
Jobs where the only check is a person
Log analysis, internal link planning and redirect mapping produce output nothing validates mechanically, because what gets judged is relevance or priority, not syntax. All three are worth delegating, and none without the gate.
Log analysis. Claude Code earns its place here most easily, because the work is much reading and little deciding. Hitches, 2 April 2026, lists five questions a log answers and a crawl export cannot: which URLs are crawled most, how often, which return errors, which important pages were never crawled, and where budget goes on URLs that do not deserve it. A log is a file, so no connector is needed, though for millions of URLs Hitches recommends aggregating in Claude Code rather than pasting the set into a conversation. The last step is not the agent’s, because “waste” is a claim about your commercial priorities.
Internal linking at scale. The agent scores candidate pairs and returns a plan with a source, a target, an anchor and an insertion point; StudioHawk, 12 May 2026, describes a three-tier scoring graph producing exactly that, as a rehearsal rather than edits. The threshold deciding which pairs qualify is the one parameter you cannot copy from another site, and calibrating it is a lesson of its own later in this level. The gate’s contribution is independent of it: the plan is a signed artefact, so the links that go live are the links a person read.
Redirect maps. A map built from embeddings gets most URLs right and is wrong exactly where it hurts. The most quoted default cutoff is 0.95, in Screaming Frog’s documentation on vector embeddings for redirect mapping, modified 20 October 2025. Tools that ship a default cutoff do not say what to do with the pairs below it, and those pairs are the migration; validating a map is a lesson of its own later in this level. What Claude Code finishes end to end is the part after approval: turning the agreed mapping into server rules, which is mechanical, deterministic and diffable. What those rules must avoid producing is in the lesson on redirect chains.
The human step is not a formality, and one dated account says what the reviewer catches. ContextBolt’s Claude SEO Experiment, 23 June 2026, signed only “David”, put a week of real SEO work through Claude and logged three interceptions: third-party estimates returned as measurements, a keyword still pushed after its own difficulty score called it unwinnable, and keyword-stuffed over-optimisation under loose instructions. One site, one week, no control, no sample size: a description of the review job, not a rate. A syntax check would have passed all three.
Where this lesson stops: how many pages is too many
As soon as Claude Code can write to your CMS, the question of how many pages is too many arrives, and this lesson does not answer it because the evidence does not. Google’s spam policy text carries a Last updated stamp of 28 August 2026, and the test it sets for scaled content abuse is the purpose behind a batch of pages: made to move rankings, or made for whoever lands on them. Read it for what is absent, because the absences are the usable part: no page count, no rate, and no mention of the tool that produced the words. A stated limit of 100 or 500 or 5,000 pages did not come from there.
From that sentence the industry splits. One camp reads it as making production method irrelevant, so the test is whether each page is verified, distinct and useful. The other reads agent-driven volume as a risk in itself, because volume and intent are hard to separate once a process can produce a hundred pages before anyone looks. No published study isolates either reading, so this lesson stops there; a pre-generation test for a template is a later lesson in this level.
The mechanism this lesson contributes does not depend on where the line sits. Creating a hundred pages should require a person signing a hundred-page plan, not finding them in next month’s crawl. Same gate, larger plan, and the signature makes “I approved this” checkable rather than remembered.
Common mistakes
- Granting write access before the audit log exists. If you cannot say what changed, you cannot reverse it precisely. The fix: log every run, rehearsals included, and let the first real write touch one file.
- Approving the run instead of the plan. An on-screen “apply?” approves whatever the process holds when you answer, not necessarily what you read. The fix: approve a signature of the plan, so one changed character refuses the write.
- Leaving the rate limit to a tool’s interface. A speed set in one place is not automatically honoured by a different execution path. The fix: put the pause inside your own write loop.
- Treating the difficulty and volume numbers a model returns as data. ContextBolt recorded on 23 June 2026 that third-party estimates came back as measurements (one site, one week). The fix: any number that decides something comes from the source that measures it.
- Exiting 0 on a refused or partial write. A run that wrote nothing looks identical to a run with nothing to do, and a scheduler believes both. The fix: one non-zero code per cause.
The short version
- Claude Code analyses crawl data and does not crawl sites (Lawrence Hitches, 2 April 2026), so every job starts from a file somebody else produced.
- The published savings are gross: 6–8 hours to minutes, 3–5 days to under an hour (StudioHawk, 12 May 2026, no sample size), deducting nothing for setup, tokens, subscription or the mandatory review.
- Net saving is
(T_manual - T_agent - T_verify) * N - T_setup - C_period, and onceT_verifyreachesT_manual - T_agentno run count makes it positive. - 1% of surveyed practitioners call their work fully automated, 1 person of 97 (Keyword.com, State of AI in SEO 2026, 1 January 2026, n=97, self-selected).
- The most-cited reason for holding back is quality, at 57%, which is neither the 70% limitations figure nor the 57% ChatGPT adoption figure on the same page.
- Five guarantees before write access: rehearsal by default, a JSONL log written during rehearsals, one write per second in your own code, a backup per change, and approval of a plan signature, not a run.
- Delegate first the jobs with a pass-or-fail command behind them: JSON-LD, generated redirect rules,
hreflangreciprocity. Log priorities, link plans and redirect targets have none. - Google’s spam policy, stamped 28 August 2026, defines scaled content abuse by purpose and publishes no page threshold. The industry disagrees and this lesson gives no verdict.
Frequently asked questions
What does a dry run have to do to be worth anything?
Be the default, and leave a record. A rehearsal you opt into is one somebody forgets, so the flagless invocation writes nothing. It also has to inspect each change rather than list it: confirm the file exists, count occurrences of the pattern, refuse an ambiguous match.
How many runs before an MCP server pays for itself?
More than T_setup divided by the per-run saving, where that saving is manual time minus agent time minus review. Two figures are published: 6–8 hours to minutes, and 3–5 days to under an hour (StudioHawk, 12 May 2026). The other three terms have no published measurement this course could locate, so time your own third run, add the review, and compare.
How many pages can an agent generate before Google objects?
Google’s spam policy text, stamped 28 August 2026, publishes no number at all. Its test is the purpose a batch of pages was made for, and it says in terms that the tool behind them is beside the point. The industry splits between reading that as making method irrelevant provided each page is verified and distinct, and reading agent-driven volume as a risk in itself. This lesson presents both and picks neither, while insisting a hundred new pages exist because somebody signed a plan for a hundred.
Sources
Sources marked (unlinked) could not be opened during this run, and are cited by publication, author and date.
- Lawrence Hitches, “Claude for Technical SEO: Audits, Crawl Fixes & Schema”, 2 April 2026. No sample size published.
- Lawrence Hitches, “How to Use Claude Code for SEO Automation”, StudioHawk, 12 May 2026. The five controls and both time figures; no sample size, no costs deducted.
- Keyword.com, State of AI in SEO 2026, 1 January 2026, n=97 usable, self-selected.
- ContextBolt, “Claude SEO Experiment: A Week of Running My Real SEO”, 23 June 2026, author given only as “David”. One site, one week, no control.
- Anthropic, Ahrefs connector for Claude, checked 20 September 2026: 61 tools, a single-day snapshot.
- Semrush, Semrush MCP documentation, updated 5 August 2026.
- Screaming Frog, documentation on vector embeddings for redirect mapping, modified 20 October 2025: default cutoff 0.95. (unlinked)
- Google Search Central, spam policies, scaled content abuse, last updated 28 August 2026. (unlinked)
- Google Autocomplete, harvested for this course on 20 September 2026: the seed
claude code seoreturned eight suggestions. Presence, never volume; no volume figure appears here because none was obtainable from a named source. - The gate script, its fixtures, plan format and transcript are original to doctor-seo.net, run 29 September 2026.
Carry on with the course
- Previous lesson: Claude Skills for SEO: Install, Use, and Write Your Own
- What comes next: the lessons after this one appear on the level page as they publish.
- Level: AI for SEO: Using the Models as Tools
- Course index: Free SEO training online course