By the end of this lesson you will have a working on-page audit skill installed: a SKILL.md, the thresholds file it reads, a report template and a script. You will also be able to price the skills you leave installed and never use.
That price is the part almost nobody counts. Anthropic’s Agent Skills documentation, checked on 23 September 2026, sets level one of progressive disclosure at “~100 tokens per Skill”, and level one loads when the session starts, not when the skill fires. Installing the 51 free SEO skills Hawk Academy publishes therefore carries roughly 5,100 tokens of metadata into every conversation, SEO or not: arithmetic over a published budget, so an order of magnitude rather than a measurement.
The previous lesson, what Claude can and cannot do for SEO, draws the scope. This one packages a process of your own so it fires when it should, thresholds in a reviewable file and a deterministic script underneath. None of it changes whether an answer engine quotes your pages, which is Level 4 on GEO and AIO.
What you’ll learn
- What a Claude skill is as a file, with the fields and limits Anthropic publishes.
- The progressive-disclosure budget, and how to use it to decide how many skills to keep.
- Why a skill installed on one Claude surface does not appear on the others.
- A complete on-page audit
SKILL.md, its thresholds file and a script with documented exit codes. - What a skill can genuinely audit from a crawl export, and what it fills in.
A Claude skill is a folder, and the folder goes somewhere specific
A Claude skill is a folder with a SKILL.md file in it, and that file opens with a YAML block carrying two mandatory fields. Anthropic’s Agent Skills documentation, checked on 23 September 2026, states it plainly: “Every Skill requires a SKILL.md file with YAML frontmatter”. The two fields are name and description.
name takes at most 64 characters of lower case, digits and hyphens, and cannot contain XML tags or the reserved words “anthropic” and “claude”. description must be non-empty and also cannot contain XML tags; its length limit gets a section of its own below, because the published figures do not agree.
What matters about description is not its length but its job: it is the only text Claude reads when deciding whether to load the skill. Anthropic’s best-practices guidance, checked the same day, asks for the third person (“Processes Excel files and generates reports”) and for both halves of the answer, what the skill does and when to use it. A vague description is a skill that never fires.
Claude Code’s documentation adds optional fields, among them allowed-tools, disallowed-tools, disable-model-invocation and paths. Hold on to disallowed-tools: it is the field that stops a diagnostic skill touching your CMS.
Where the folder goes depends on the surface, and a skill installed on one does not appear on the others. The paths come from Claude Code’s documentation and Anthropic’s help centre, checked on 23 September 2026.
| Surface | Where it goes | Format | Scope |
|---|---|---|---|
| claude.ai | Customize > Skills, then “Upload a skill” | ZIP, skill folder as root | Per user; Team and Enterprise can provision org-wide |
| Claude Code, personal | ~/.claude/skills/<name>/SKILL.md |
Folder | Every session on that machine |
| Claude Code, project | .claude/skills/<name>/SKILL.md |
Folder, versioned in git | Everyone who clones the repo |
| Claude Code, plugin | <plugin>/skills/<name>/SKILL.md |
Folder | Invoked as /plugin-name:skill-name |
| API | Skills API upload | Skill package | No network access, no package installs at run time |
Skills on claude.ai need code execution switched on: Settings > Capabilities on Free, Pro and Max, and the policy tab under Organization settings > Plugins & skills on Team and Enterprise. Anthropic’s help centre lists skills as available on all five plans.
Per-user scope is what surprises teams. A skill you upload to claude.ai stays in your account; handing it to everyone is a separate procedure with its own help-centre article, “Provision and manage skills for your organization”, limited to Team and Enterprise. For a team auditing to one standard, the route that synchronises itself is .claude/skills/ in the repository, where the skill’s change history is the git history.
What an installed skill costs before you ask anything
Progressive disclosure lets a skill be large without occupying context until needed, and Anthropic documents three levels with a declared cost for each. The table reproduces the values on its Agent Skills page, checked on 23 September 2026.
| Level | When it loads | Token cost | What it holds |
|---|---|---|---|
| 1. Metadata | Always, at session start | “~100 tokens per Skill” | name and description from the YAML block |
| 2. Instructions | When the skill fires | “Under 5k tokens” | The body of SKILL.md |
| 3. Resources | Only when needed | “None until accessed” | Reference files, scripts, data |
The operational consequence sits in the first row. Level one is charged per installed skill, in every conversation, used or not; level two only when the description matches the request; level three not until Claude opens the file. A skill with 400 lines of instructions and 30,000 words of reference files costs the same as an empty one while it sleeps, but ten installed skills cost ten times level one even when none fires.
A shape limit sits on top: Anthropic’s best-practices guidance says “Keep SKILL.md body under 500 lines for optimal performance”, splitting anything beyond that into separate files kept one level deep. One installation rule of my own follows: if you have not invoked a skill in 30 days, it leaves ~/.claude/skills/ for the repository.
The audit skill, file by file
The skill below audits a crawl export and returns prioritised findings. Copy it into ~/.claude/skills/onpage-crawl-audit/SKILL.md.
---
name: onpage-crawl-audit
description: Audits a crawl export and returns prioritised on-page findings with URL, cell value and threshold. Use when a crawl CSV is attached or titles, canonicals or orphan pages need review.
disallowed-tools: Write, Edit, WebFetch
---
# On-page audit from a crawl export
## Rule zero
This skill does not crawl sites. It analyses the file the user supplies.
If no file is attached, stop and ask for one, naming the minimum columns.
Do not estimate absent values, do not invent URLs, do not fill in a
missing column.
## Inputs
Required: a Screaming Frog `internal_all` CSV export, or another crawler's
equivalent with its column names added to the script's alias table. Minimum
columns:
Address, Status Code, Indexability, Title 1, Title 1 Length,
Meta Description 1, H1-1, H1-2, Canonical Link Element 1,
Word Count, Inlinks, Crawl Depth
Optional: a Search Console pages export (page, clicks, impressions,
position). Pass it as the script's second argument and it is joined to the
crawl on exact URL. Without it, the rules that depend on `clicks` are not
evaluated and the report has to say so.
If a required column is absent, name it and continue with the checks you
can still make. Never substitute an estimate for a missing column.
## Procedure
1. Normalise the files:
python3 scripts/normalise_crawl.py crawl.csv [search-console.csv] > crawl.json
The script maps the column names above onto short field names, joins the
Search Console data when it is there, and returns one row per URL.
Standard library only. The `with_gsc` field says whether a second file
was read; `missing_columns` names the required columns it did not find,
and every threshold that consumes one of them is then unevaluated. Exit
code 6 means it recognised no column name at all, which usually means
the delimiter is wrong rather than the crawl being empty.
2. Define the universe. Keep URLs with `status` 200 and `indexability`
equal to Indexable. Count what falls out and why. Every later
percentage is calculated over that universe, and the report states the
size of the universe before it gives its first percentage.
3. Read `THRESHOLDS.md` and apply those thresholds. A finding that fits no
documented threshold is not reported as an error: it goes at the end of
the report as an observation.
4. Prioritise. P1: the URL cannot be indexed, or it is losing traffic
already measured in Search Console. P2: the URL is indexable but the
template is cloned or the page is orphaned. P3: everything else.
5. Write it up with the `REPORT.md` template. One row per finding, with
the full URL and the exact cell value that triggered it.
## What this skill does not report
- Search volume, keyword difficulty or CPC. None of it is in a crawl, and
an invented number shaped like data is worse than a gap.
- Keyword density. It is not a Google metric.
- Proprietary quality, authority or toxicity scores.
- Any figure that does not come from a cell in the file, or from a
calculation over cells in the file.
## Final check, mandatory before delivering
- [ ] Every finding cites its full URL and the exact cell value
- [ ] The report's URL count reconciles with the input file's
- [ ] Every threshold applied is in THRESHOLDS.md
- [ ] Every threshold naming a column in `missing_columns` is reported
unevaluated, not as zero findings
- [ ] No rule depending on `clicks` was evaluated with `with_gsc` false
- [ ] No figure in the report came from the model
- [ ] The report states the crawl date and the tool that produced it
## Files in this skill
- `THRESHOLDS.md`: the thresholds and where each comes from. Read at step 3.
- `REPORT.md`: the output template. Read at step 5.
- `scripts/normalise_crawl.py`: column normaliser and Search Console join.
Three decisions separate that file from a long prompt. disallowed-tools is an optional Claude Code field its documentation defines as the tools removed from the available set while the skill is active; an audit skill has no need to write files or fetch pages, and withdrawing those capabilities turns a good intention into an enforced restriction.
“Rule zero” comes next. An SEO skill nearly always fails in the same place: it fills what the data does not contain with something plausible. Stopping to ask for the file saves more review time than anything else in the folder.
And the body of the SKILL.md carries no threshold at all. They live in THRESHOLDS.md, level three, costing no context until Claude opens it at step 3, so the judgement stays versioned in a file you can review.
Deciding a description length you can defend on both surfaces
Decide the length before you write the description, because the published figures do not agree. Anthropic’s Agent Skills platform documentation gives description 1,024 characters and writes the filename SKILL.md; its help-centre article “How to create custom skills”, updated 22 July 2026, gives 200 and writes skill.md. Both were checked on 23 September 2026.
Documentation for agent file formats routinely publishes different limits on different pages: a platform reference, a help-centre walkthrough and a product surface describe different execution contexts, rarely versioned together. Treat that as a property of the category, not a puzzle to solve, and pick the number correct on either reading, which means the smaller one. So write the description under 200 characters; the skill above uses 182. Name the file SKILL.md in capitals, the form the platform and Claude Code documentation both use, since the difference from the help centre’s skill.md is invisible on macOS and decisive on Linux. Two hundred well-chosen characters fire a skill as well as nine hundred: what works is whether the description holds the words the person who needs it will type.
Where the judgement lives: a thresholds file you can argue with
THRESHOLDS.md is the skill’s reference file, and the part of this lesson you can run today with or without Claude in the loop: a threshold table applicable to a Screaming Frog export as it stands.
# Thresholds
Every threshold carries its origin, and all of them are computed over the
fields `normalise_crawl.py` produces. Change the numbers in this file and
never in the conversation, so the judgement stays versioned and next
month's audit is comparable with this one.
| Check | Threshold | Priority | Where it comes from |
|---|---|---|---|
| `status` not 200 with `inlinks` > 0 | any | P1 | An internal link points at something that does not answer |
| `indexability` not Indexable with `clicks` > 0 | any | P1 | Measured traffic being lost |
| `title` empty | any | P1 | With no source text, the engine rewrites |
| `title` identical on 2 or more URLs | >= 2 | P2 | Sign of a cloned template |
| `title_len` | < 15 or > 60 characters | P3 | House threshold, not a Google rule |
| `metadesc` empty or duplicated | any | P3 | Reported as a count, not as an error |
| `h1` empty, or `h1_2` populated | any | P2 | Missing H1, or more than one |
| `canonical` not equal to `url` with `clicks` > 0 | any | P1 | A URL that performs is being withdrawn |
| `words` < 300 across one `section` | >= 10 URLs | P2 | A section with no distinct content |
| `inlinks` = 0 with `status` 200 and indexable | any | P2 | Discovered and not linked |
| `depth` > 4 with `clicks` > 0 | any | P3 | Real traffic buried in the architecture |
## Notes
- The three rules using `clicks` need the second file: indexability with
traffic, canonical with traffic, and depth with traffic. With `with_gsc`
false they are not evaluated and the report says so explicitly.
- Any rule whose column appears in `missing_columns` is unevaluated too,
and is reported that way rather than as zero findings.
- `inlinks` is the total count of inbound links, not the count of unique
linking URLs: that is `unique_inlinks`, kept separately because the
Screaming Frog export carries both columns.
- `section` is the first path segment, computed by the script. It
approximates "the same template"; it does not prove it.
- The orphan rule is about `inlinks` alone. Crawl depth is a separate
check, and a page can be orphaned at any depth.
- The orphan rule only means something if the crawl included the sitemap
or a URL list. In a link-following crawl, `inlinks` = 0 can only happen
to the start URL.
- `metadesc`: the industry does not agree on whether writing them one by
one repays the effort, nor on how much weight to give the keyword. This
skill counts and lists them. It does not recommend rewriting them in
bulk and does not treat them as a serious error.
- `title_len`: the real cut-off is in pixels and varies by device. 15 and
60 are working thresholds. If you change them, date the change here.
Look at the metadesc note: the skill counts them rather than flagging them, because whether hand-writing meta descriptions repays the effort is an argument this course does not settle. A threshold that hides a disputed judgement under the label “error” manufactures work nobody needed.
The same caution applies to a check several published SEO skills ship: a pass or fail on whether a page is “ready for AI search”. Whether GEO is a discipline of its own or ordinary SEO renamed is genuinely disputed. One camp reads the citation evidence as showing that AI answer surfaces mostly reward what classic search already rewarded, the same job with passage-level polish. The other reads it as showing that at least one major assistant retrieves from an index of its own, on terms that do not track the organic results, which would be a separate job. The course takes no position on it. So a rule failing a page on a GEO check asserts a verdict the evidence does not support: put it in observations, not the error column. Level 4 sets out both camps.
The script, its exit codes, and a real run
The deterministic piece is a normaliser. It maps the Screaming Frog column names onto short field names, with a few common aliases, so extend the map for your own crawler rather than assuming it is covered. It joins a Search Console export on exact URL, and computes the path segment the template threshold relies on. It uses nothing but the Python standard library, because skills on the Claude API have no network access and cannot install packages at run time.
Anthropic’s best-practices guidance asks for error handling in so many words: “When writing scripts for Skills, handle error conditions rather than deferring to Claude”. That means a documented exit code per failure, and no code carrying two meanings, which is harder than it sounds: a wrong delimiter and a genuinely empty crawl both produce zero rows, and only one of them is a finding.
| Exit code | Meaning |
|---|---|
| 0 | Normalised JSON on stdout |
| 2 | Wrong number of arguments |
| 3 | An input path is missing, is not a regular file, or could not be opened |
| 4 | A file is not UTF-8 text, or the address column held something that is not an absolute URL |
| 5 | The header was recognised but no row carried a URL |
| 6 | No recognised column names at all: check the format and delimiter |
#!/usr/bin/env python3
"""Normalise a crawl export into one JSON row per URL, and optionally join it
to a Search Console page export.
python3 normalise_crawl.py crawl.csv [search-console.csv] > crawl.json
Standard library only, so the same file runs in Claude Code and on the Skills
API, where no package can be installed at run time.
Exit codes:
0 Normalised JSON on stdout.
2 Wrong number of arguments.
3 An input path does not exist, is not a regular file, or could not be opened.
4 A file is not UTF-8 text, or the address column held a value that is not a
well-formed absolute URL.
5 The header was recognised but no row carried a URL.
6 No recognised column names at all: check the export format and delimiter.
"""
import csv
import json
import os
import sys
from urllib.parse import urlsplit
CRAWL_ALIASES = {
"address": "url", "url": "url",
"status code": "status", "http status code": "status",
"indexability": "indexability",
"title 1": "title", "page title": "title",
"title 1 length": "title_len", "page title length": "title_len",
"meta description 1": "metadesc",
"meta description 1 length": "metadesc_len",
"h1-1": "h1", "h1": "h1",
"h1-2": "h1_2",
"canonical link element 1": "canonical", "canonical url": "canonical",
"word count": "words", "words": "words",
"inlinks": "inlinks",
"unique inlinks": "unique_inlinks",
"crawl depth": "depth", "depth": "depth",
}
GSC_ALIASES = {
"url": "url", "address": "url", "page": "url", "top pages": "url",
"clicks": "clicks", "impressions": "impressions", "position": "position",
}
INTEGER_FIELDS = {"status", "title_len", "metadesc_len", "words",
"inlinks", "unique_inlinks", "depth", "clicks",
"impressions"}
REQUIRED_FIELDS = ("url", "status", "indexability", "title", "title_len",
"metadesc", "h1", "h1_2", "canonical", "words",
"inlinks", "depth")
def to_int(value):
"""Accept 1234, 1,234 and 1.234. Return None when there is no digit."""
digits = "".join(c for c in str(value) if c.isdigit())
return int(digits) if digits else None
def read_csv(path, aliases):
"""Return (rows, the set of known fields named in the header)."""
rows, found = [], set()
with open(path, newline="", encoding="utf-8-sig") as handle:
reader = csv.DictReader(handle)
for column in reader.fieldnames or []:
field = aliases.get((column or "").strip().lower())
if field:
found.add(field)
for raw in reader:
row = {}
for column, value in raw.items():
field = aliases.get((column or "").strip().lower())
if field is None:
continue
row[field] = to_int(value) if field in INTEGER_FIELDS else value
if row.get("url"):
rows.append(row)
return rows, found
def section(url):
parts = [part for part in urlsplit(url).path.split("/") if part]
return "/" + parts[0] + "/" if parts else "/"
def fail(code, message):
sys.stderr.write(message + "\n")
raise SystemExit(code)
def main(argv):
if not 2 <= len(argv) <= 3:
fail(2, "usage: normalise_crawl.py crawl.csv [search-console.csv]")
for path in argv[1:]:
if not os.path.exists(path):
fail(3, "input file not found: %s" % path)
if not os.path.isfile(path):
fail(3, "not a regular file: %s" % path)
try:
crawl, found = read_csv(argv[1], CRAWL_ALIASES)
gsc_rows = read_csv(argv[2], GSC_ALIASES)[0] if len(argv) == 3 else []
except OSError as error:
fail(3, "could not open input file: %s" % error)
except UnicodeDecodeError as error:
fail(4, "not UTF-8 text: %s" % error)
except csv.Error as error:
fail(4, "could not parse CSV: %s" % error)
if not found & set(REQUIRED_FIELDS):
fail(6, "no recognised column names in %s: check the export format "
"and delimiter" % argv[1])
if not crawl:
fail(5, "no row carrying a URL in %s" % argv[1])
for row in crawl:
parts = urlsplit(row["url"])
if not parts.scheme or not parts.netloc or any(c.isspace()
for c in row["url"]):
fail(4, "not a well-formed absolute URL: %r" % row["url"])
gsc = {row["url"]: row for row in gsc_rows}
for row in crawl:
row["section"] = section(row["url"])
matched = gsc.get(row["url"], {})
row["clicks"] = matched.get("clicks")
row["impressions"] = matched.get("impressions")
return {"rows": crawl,
"n": len(crawl),
"with_gsc": bool(gsc),
"missing_columns": [f for f in REQUIRED_FIELDS if f not in found]}
if __name__ == "__main__":
json.dump(main(sys.argv), sys.stdout, ensure_ascii=False, indent=1)
sys.stdout.write("\n")
Run it on a two-row export with a Search Console file beside it and this is the terminal output. The second URL has no Search Console row, so its clicks and impressions come back as null rather than zero, which is the distinction the thresholds need.
{
"rows": [
{
"url": "https://example.com/shoes/",
"status": 200,
"indexability": "Indexable",
"title": "Shoes",
"title_len": 5,
"h1": "Shoes",
"h1_2": "",
"canonical": "https://example.com/shoes/",
"words": 820,
"inlinks": 34,
"depth": 1,
"section": "/shoes/",
"clicks": 412,
"impressions": 9034
},
{
"url": "https://example.com/shoes/blue/",
"status": 200,
"indexability": "Indexable",
"title": "Shoes",
"title_len": 5,
"h1": "",
"h1_2": "",
"canonical": "https://example.com/shoes/",
"words": 210,
"inlinks": 0,
"depth": 3,
"section": "/shoes/",
"clicks": null,
"impressions": null
}
],
"n": 2,
"with_gsc": true,
"missing_columns": [
"metadesc"
]
}
Two rows, five findings: a duplicated title across two URLs, two titles under fifteen characters, an empty H1, and an orphan. The canonical rule does not fire, because row two has no measured traffic and that rule needs clicks above zero. The metadesc rule is unevaluated too, and must be reported that way rather than as zero findings, because missing_columns names the column the export did not carry.
Two failures show the codes working: a path that does not exist, then a tab-separated export. Each names the problem it hit, without a traceback.
$ python3 scripts/normalise_crawl.py crawl-2026-09.csv
input file not found: crawl-2026-09.csv
$ echo $?
3
$ python3 scripts/normalise_crawl.py crawl.tsv
no recognised column names in crawl.tsv: check the export format and delimiter
$ echo $?
6
The fourth file, REPORT.md, stops the output changing shape every month: a header, one row per finding, a summary by check covering the clean and the unevaluated ones, observations, and a closing list of what the report does not cover. Then zip the folder with the skill directory as its root, the structure Anthropic’s help centre requires.
onpage-crawl-audit/
SKILL.md
THRESHOLDS.md
REPORT.md
scripts/
normalise_crawl.py
zip -r onpage-crawl-audit.zip onpage-crawl-audit/
What a skill can actually audit, and what it fills in
Claude analyses crawl data; it does not crawl sites. The formulation is Lawrence Hitches’s, in Claude for Technical SEO: Audits, Crawl Fixes & Schema of 2 April 2026, a professional-practice article that publishes no sample size: “Claude analyses crawl data. It doesn’t crawl sites”. That is the hard boundary on any audit skill, and why “Rule zero” comes first. A skill audits what is computable over cells in an export: status codes, indexability, duplicated titles and H1s, canonicals pointing away, orphan pages, crawl depth. It fetches no URLs, executes no JavaScript and knows no search volumes. Producing the export is ordinary technical SEO.
The best public breakdown of how many people hand audits over is State of AI and Automation in SEO, Keyword.com, 1 January 2026, n=97 usable responses: 38% named technical SEO audits among the tasks they use AI for, and among the 79% who said there were tasks they could automate but had chosen not to, 40% named technical SEO. Small and self-selected: it orders tasks, it does not measure the profession.
That refusal has documented cases behind it. ContextBolt‘s week-long trial of 23 June 2026 records three failure modes: the model treated third-party estimates as factual data, over-optimised with repeated keywords when instructions were loose, and proposed a non-existent markup for “ranking in AI”. On the first it kept pushing a keyword whose own difficulty score said it was unwinnable. One site, one week, author given only as “David” with no surname and no sample size: failure modes, not their frequency.
All of which is why disallowed-tools is the first line of the skill above rather than an afterthought. A skill that can write is a skill that can publish, and the one public account of what that costs is Will Scott’s, in Search Engine Land on 28 August 2026: an assistant asked for two landing pages returned two copies of the homepage with fresh title tags, both settled at zero impressions and zero clicks, the homepage stayed at position nine or worse, and a second site repeated the pattern. One author and two sites, no sample size published, so the case sizes nothing. Its use here is narrower: it names the capability an audit skill has no business holding. Reading a CSV and writing a report into the conversation is the whole job, so Write, Edit and WebFetch all go in the header. Which findings to act on first belongs with SEO analytics.
Why prompt listicles stopped working when Skills shipped
A long prompt and a skill do not compete on how well they are written. They compete on when they load. A 2,000-word prompt costs its 2,000 words in every conversation where you remember to paste it, and nothing in the ones where you forget, which are the conversations that produce the bad work. A skill costs roughly 100 tokens of metadata always, and loads its instructions only when its description matches the request. Anthropic publishes no words-to-tokens equivalence, so those 2,000 words are not converted here: the asymmetry is about when, not how much.
Four things follow from that budget rather than from any product claim. Level one is always resident, so the skill is matched to a request without your naming it. Level three is not loaded until opened, so reference files cost nothing standing. Those files can be scripts, so steps can be deterministic instead of re-reasoned. And the whole thing is a folder, so it is versioned and reviewed in a pull request.
The Google Autocomplete harvest run for this course on 20 September 2026 shows both intents alive at once. “claude prompts for seo” returned 7 variants, top 20 claude prompts for seo among them, so the audience for lists has not gone anywhere. “claude seo” returned 10 suggestions headed by claude seo skill; “claude seo audit” returned 10 of which 6 were skill, GitHub or tool variants; “claude skills seo” added claude skills seo audit, claude skills seo geo and claude seo skill md. Somebody typing claude seo skill md has stopped wanting the text and started wanting the file. Autocomplete proves presence, not volume, and no named source publishes a volume figure for these terms.
Which raises where the files come from. Agencies and individuals publish SEO skills, not Anthropic. The official anthropics/skills repository held no SEO skill on 20 September 2026, while AgriciDaniel/claude-seo and Hawk Academy’s collection did. Hawk Academy, created by StudioHawk on its own statement, advertises 51 and carries no publication date, only a 2026 copyright line; its repository lhitches/claude-seo-skills still advertises 29, so 51 is the page’s figure, not the repository’s. Counts and thresholds both come from somebody you have not met.
None of that makes prompts useless. What expired is a format: the article of 47 prompts you paste by hand. If the prompt is good, its home is the body of a SKILL.md.
Common mistakes
- Installing thirty skills in case you need them. Each costs roughly 100 tokens of metadata in every conversation, used or not (Anthropic documentation, checked 23 September 2026): thirty dormant skills are on the order of 3,000 tokens before you ask anything. The fix: keep this month’s installed and the rest in a repository.
- Writing a generic description. “Helps with SEO tasks” never fires, because that sentence is what Claude compares against your request. The fix: third person, what it does and when to use it, in the words you actually type: crawl CSV, duplicate titles, canonicals, orphan pages.
- Giving a diagnostic skill write access. The case Will Scott reported in Search Engine Land on 28 August 2026 documents two cloned URLs with zero impressions and zero clicks, across two sites and one author rather than a sample. The fix:
disallowed-toolsin the header. - Letting the skill produce numbers that are not in the file. ContextBolt recorded on 23 June 2026 that the model treated third-party estimates as factual data. The fix: a final check forcing every figure to cite the cell it came from.
- Reporting an unevaluated check as a clean one. A threshold whose column is missing, or which needs Search Console data you did not supply, found nothing because it never ran. The fix: read
missing_columnsandwith_gsc, and print those checks unevaluated.
The short version
- A Claude skill is a folder with a
SKILL.mdwhose YAML block has two mandatory fields:name, at most 64 characters, anddescription, non-empty. - Progressive disclosure has three declared costs: roughly 100 tokens of metadata always, under 5,000 tokens of instructions when the skill fires, nothing for resources until opened. Level one is charged per installed skill in every conversation, so the number you keep installed is a cost decision.
- Anthropic’s platform documentation puts
descriptionat 1,024 characters and its help-centre article, updated 22 July 2026, at 200. Write under 200 and the skill is correct on either reading. - The three surfaces install by different routes and do not synchronise: a ZIP on claude.ai,
.claude/skills/in Claude Code, a Skills API upload with no network access. - Claude analyses crawl data and does not crawl sites (Lawrence Hitches, 2 April 2026), so an audit skill asks for the export and never substitutes an estimate.
- Every script needs a documented exit code per failure: a missing input file, an unreadable one and an unrecognised column set are three problems needing three codes.
- A good prompt is no longer a list. It is a
SKILL.md, thresholds in a separate file, with a final checklist.
Frequently asked questions
Does a skill I install on claude.ai also work in Claude Code?
No. On claude.ai you upload a ZIP from Customize > Skills, in Claude Code you place a folder in ~/.claude/skills/ or the project’s .claude/skills/, and on the API you upload through the Skills API. One surface does not feed the others. For a team the least friction is .claude/skills/ in the repository, where the skill and its history travel with the code.
Can a Claude skill crawl my website?
Not by itself. A skill is an instruction file plus its resources; it carries no crawler, and on the API skills have no network access at all. Crawl, export, hand the export to the skill. Making Claude trigger the crawl needs an MCP server connected to your crawler, another lesson in this level.
Is it worth writing my own skill when repositories publish hundreds?
It depends on whether you can audit their thresholds. Somebody else’s SEO skill carries disputable judgements presented as errors: which title length is a problem, which crawl depth is excessive, whether a page passes an AI-readiness check the industry has not agreed on. If those numbers sit in a reference file you can read and change, install it. If they are buried in the instructions, write your own.
Sources
- Anthropic, Agent Skills platform documentation, its best-practices guidance for
SKILL.mdand the Claude Code skills documentation: file format, thenameanddescriptionlimits, the progressive-disclosure budget, the 500-line guideline, the optional frontmatter fields, the install paths and the instruction to handle error conditions in scripts. Checked 23 September 2026. - Anthropic help centre: “How to create custom skills”, updated 22 July 2026; “Use skills in Claude”; “Provision and manage skills for your organization”. Checked 23 September 2026. Plus Anthropic, “How to create Skills: key steps, limitations and examples”, 19 November 2025.
- The Anthropic pages above ship named and dated without links: none could be opened for verification here.
- GitHub counts of 20 September 2026, single-day counts rather than a series:
anthropics/skills, about 168,900 stars, no SEO skill present;AgriciDaniel/claude-seo, the most popular community SEO skill repository, about 16,700 stars, about 2,400 forks, version 2.3.1 of September 2026. - Hawk Academy, created by StudioHawk, free Claude SEO skills. Advertises 51; no publication date, only a 2026 copyright line; its repository
lhitches/claude-seo-skillsstill advertises 29. - Lawrence Hitches, Claude for Technical SEO: Audits, Crawl Fixes & Schema. 2 April 2026. Professional-practice article; publishes no sample size.
- ContextBolt, Claude SEO Experiment: A Week of Running My Real SEO. 23 June 2026. One site over one week; author given only as “David”, no surname and no sample size published.
- Keyword.com, State of AI and Automation in SEO. 1 January 2026, n=97 usable and self-selected responses. Small and directional.
- Will Scott, Use Claude for SEO. Don’t let Claude do SEO., Search Engine Land. 28 August 2026. Two sites documented, with no sample size published. No link: the page could not be opened for verification here.
- Google Autocomplete, harvested for this course on 20 September 2026. Evidences query presence, not volume.
Continue the course
- Previous lesson: What Claude Can and Cannot Do for SEO
- Next lesson: Claude Code for Technical SEO (not yet published)
- Level: AI for SEO: Using the Models as Tools
- Course index: Free SEO training online course