> ## Content Index
> Fetch the complete content index at: https://biospherelabs.ai/llms.txt
> Use this file to discover other available public pages before exploring further.

# Your Skill Only Works Because Claude Is Smart
- URL: https://biospherelabs.ai/writing/tutorials/analysing-skills-for-cross-model-usage/
- Published: 2026-07-06T00:00:00.000Z
- Updated: 2026-10-28T14:39:25.000Z
- Author: Justin Robinson
- Tags: Tutorials

I have a skill that writes illustrated blog articles. Under Claude Code it does the whole job: researches the topic, drafts in my voice, makes the images, checks its own work and hands me something I can publish. So I tried the obvious thing, which was to run it on something cheaper. A different harness and a smaller model.

It made a mess, and it put my name on it.

What I found strange was how it failed. It called a tool named `view_file` that didn't exist in that harness, so none of the images were ever checked. It wrote three em dashes into the article and then reported "no em dashes" in its final checklist, because the checklist asked it to confirm, not to check. And one image loop kept going until I killed the session. The skill said "retry up to 3 times" and never said what to do after the third failure.

None of that is really the small model's fault. The skill was written for Claude, which means it was full of gaps that only a strong model fills in without being asked. So I went back through it to find those gaps. What came out of that was a list of the constructs that break under a weaker model, a small Python linter that flags them, and a rewrite of the skill that passes the linter with zero findings. The linter and both versions of the skill are in the appendix.

## Loading the file is the easy part

As a format, skills really are portable now. The [Agent Skills specification](https://agentskills.io/specification?ref=biospherelabs.ai) defines a folder with a `SKILL.md` file, YAML frontmatter carrying a `name` and a `description`, and a progressive disclosure model. The agent loads only the metadata at startup (around 100 tokens per skill), and then reads the full body when a task matches. As of mid-2026 the same folder loads in Claude Code, Codex, Gemini CLI, Cursor and a long list of other harnesses. [Anthropic's engineering post](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills?ref=biospherelabs.ai) frames skills as onboarding material for agents, and that framing fits.

But the spec only standardizes the envelope. It says nothing about the reader. My skill file parsed perfectly everywhere. What varied was how capable the thing executing it was, and my file quietly assumed the most capable one.

The analogy that made the audit click for me is a kitchen one. My original skill was a constraint list: banned words, folder naming rules, image spacing rules and a final quality checklist. It had no procedure, no step one, step two, step three. Handing that to a model is like handing a cook the health code instead of the recipe. An experienced chef reads the health code and cooks a meal that complies, because the chef already knows how to cook. A new line cook stands there holding the health code and an empty pan. Claude has cooked before. A 13B-active model hasn't, at least not your dish.

That size figure isn't hypothetical. My target executor is [DeepSeek V4 Flash](https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash?ref=biospherelabs.ai), released April 2026\. It's a 284B mixture-of-experts model that activates only about 13B parameters per token. It's fast and cheap and good at following recipes, and it's bad at deriving recipes from a pile of constraints. The research backs this up, which I found uncomfortable. One [empirical study of constraint compliance in code generation](https://arxiv.org/pdf/2604.07192?ref=biospherelabs.ai) found models correctly restating a constraint in their planning output and then violating it in the code they emitted moments later. Models also can't reliably self-correct without an external signal. That's the core finding behind [CRITIC](https://arxiv.org/pdf/2305.11738?ref=biospherelabs.ai): self-correction works when the model gets tool feedback, and mostly doesn't when it just re-reads its own output.

So "is my skill portable?" is really two questions. Does the file load? Yes, the spec guarantees that. Does the behavior survive a weaker executor? Nobody guarantees that, and mine didn't.

A list of constraints

Strong model

Derives the procedure and complies

Weaker model

Guesses, then reports success

The same skill file in two executors. A list of constraints only works when the model can work out the procedure for itself.

## A taxonomy of the breakage

I reconstructed a condensed version of my original skill as `naive-skill.md` (in full in the appendix), our specimen for the rest of the article. Here's the shape of it, trimmed:

```markdown
# Blog Tutorial Writer

You write engaging technical tutorials. Follow these rules:

- Writing must be "windowpane prose": invisible, grounded.
- Use the 4-MAT model: Why, What, How, What If.
- Avoid em dashes entirely. Avoid the Rule of Three.
- Never use the word "Tutorial" in the folder name or title.
- For prose style details, see the humanizer skill.

## Image verification

After generating an image, use view_file to inspect the result.
Compare it to the prompt. If it does not match, adjust the prompt
and regenerate. Retry up to 3 times per image.

## Before finishing

Output this checklist in your thoughts and confirm each item:
...
Make sure everything is perfect before you finish.

```

Going through my session logs and this file, the failures sorted into eight patterns. Each one looks innocent on its own.

**Constraints without a workflow.** Everything in the naive skill is a quality bar. Nothing says what to do first. A strong model infers the missing procedure (research, outline, draft, verify). A weak one starts typing prose straight away and treats the constraints as decoration.

**Missing phases that live in the author's head.** I told people the skill was for "researching a topic and writing it up." The word "research" appeared nowhere in the file. The strong model did research anyway, because researching before writing is obvious to it. The weak model did exactly what was written, which was nothing.

**Harness-specific tool names.** `view_file` is one harness's name for reading a file. Claude Code calls it Read, and other harnesses differ again. Naming a tool ties your skill to one runtime as surely as hardcoding a path.

**Verification in a hidden channel.** "Output this checklist in your thoughts" assumes there is a thinking channel and that anyone can act on what happens in it. Many models have no such channel. Even when they do, nothing downstream can see it.

**Self-reported checks.** "Make sure everything is perfect" invites exactly the failure the constraint-compliance research documents: the model says YES over a file with three em dashes in it. Self-report is what small models are worst at, and it gets worse exactly when you need it most.

**Retry loops with no exit.** "Retry up to 3 times", and then what? Undefined behavior is where weak models either loop forever or quietly accept garbage. Both happened to me.

**Cross-references to sibling skills.** "See the humanizer skill" only works if that skill is installed and loaded on the executing harness. On a fresh machine it's a dangling pointer, and whatever that other file knew is simply lost.

**Vague trigger descriptions.** My frontmatter description read like a LinkedIn bio ("an expert technical blog tutorial writer utilizing loop engineering..."). In progressive disclosure the description is the routing layer, so it needs trigger conditions, inputs and outputs. Mine were vague.

## Building the linter

yes

none

Skill file

Linter

Findings?

Rewrite the flagged construct

Zero findings

The audit as a loop: lint, rewrite what was flagged, lint again until nothing is.

Six of those eight patterns are textual. They have surface signatures a regex can catch, which means the audit doesn't have to depend on my attention span. So I wrote a linter. We'll build it up the way I actually did, starting with the single check that caused the most damage.

```python
#!/usr/bin/env python3
"""Flag harness-specific tool names in a SKILL.md file."""
import re, sys

HARNESS_TOOLS = [
    "view_file", "str_replace_editor", "read_file", "invoke_subagent",
    "run_terminal_cmd", "codebase_search", "browser_action",
]

def check_tool_names(lines):
    pattern = re.compile("|".join(re.escape(t) for t in HARNESS_TOOLS))
    for n, line in enumerate(lines, 1):
        if pattern.search(line):
            yield n, "harness-specific tool name", line.strip()

if __name__ == "__main__":
    lines = open(sys.argv[1]).read().splitlines()
    findings = list(check_tool_names(lines))
    for n, rule, text in findings:
        print(f"line {n:>3}  [{rule}]  {text[:70]}")
    sys.exit(1 if findings else 0)

```

Running it against the specimen:

```bash
$ python3 skill_lint.py naive-skill.md
line  23  [harness-specific tool name]  After generating an image, use view_file to inspect the result. Compar

```

One check, and one real catch. The full version adds five more detectors, each a direct translation of a taxonomy entry. The interesting ones are the retry check, which flags a bounded retry only when no exit path appears within the next three lines, and two file-level checks that fire on absence rather than presence: no workflow heading anywhere, and a frontmatter description with no "use when" trigger.

```python
# Verification the harness cannot observe: a hidden reasoning channel.
HIDDEN_CHANNEL = re.compile(
    r"in your (thoughts|thinking|head)|silently (confirm|check)", re.I)

# Self-reported quality gates with no command to run.
SELF_REPORT = re.compile(
    r"\b(make sure|double-check|confirm that|ensure) (that )?"
    r"(everything|each|all|it)\b", re.I)

# Retry instructions, flagged unless an exit path appears within 3 lines.
RETRY = re.compile(
    r"\b(retry|regenerate|try again).{0,40}\b(up to|max(imum)?)\s+\d", re.I)
EXIT_PATH = re.compile(
    r"\b(otherwise|if (it )?still|after that|then switch|give up"
    r"|omit|fall back|report)\b", re.I)

# References to sibling skills that may not be loaded on this harness.
CROSS_REF = re.compile(
    r"\bsee the [\w-]+ skill\b|\brefer to the [\w-]+ skill\b", re.I)

```

The full source, about 70 lines and standard library only, is in the appendix. Here's the complete run against the naive skill, output pasted from my terminal:

```bash
$ python3 skill_lint.py naive-skill.md
line  18  [cross-reference to sibling skill]  - For prose style details, see the humanizer skill.
line  19  [cross-reference to sibling skill]  - For image discipline, see the my-visual-content-generation
line  23  [harness-specific tool name]  After generating an image, use view_file to inspect the resu
line  25  [retry loop with no exit path]  Retry up to 3 times per image.
line  29  [verification in hidden channel]  Output this checklist in your thoughts and confirm each item
line  37  [self-reported check, no command]  Make sure everything is perfect before you finish.
file      [no workflow section]  constraints only, no ordered steps
file      [description has no trigger condition]  An expert technical blog tutorial writer utilizing loop engi

8 finding(s)

```

Eight findings. Every one matches a real failure I watched happen in a session log. That's a strange feeling, like getting a compiler error for a bug that already cost you a weekend.

## The rewrite, finding by finding

A linter that only complains is half a tool. The other half is knowing what the fixed construct looks like. I rewrote the skill (the condensed version is in the appendix), and the changes come down to a few repeatable moves.

**Constraint list becomes numbered procedure.** The rewrite opens with "Execute the steps below in order" and a Step 1 through Step 7 skeleton: research, outline, draft, verify code, images, mechanical checks, report. The constraints didn't go away. They moved inside the steps where they apply, and that matters more than it looks. Small models drift over long generations, so a ban stated once at the top of the file is forgotten by word 2,000 of the article. Restating the prose rules inside the drafting step puts them right next to the work they govern.

**Tool names become capability descriptions.** Instead of `view_file`:

```markdown
Open the image file with whatever file-reading capability your
environment provides. If your model has no vision, send the image
to a vision model API and ask for a neutral description, then
compare it against the prompt and the adjacent article text yourself.

```

Every harness can resolve "whatever file-reading capability your environment provides" to its own tool. And because of the no-vision branch, the instruction still ends in an action on a model that can't see at all.

**Self-report becomes a command whose output must be empty.** This is the most valuable change in the whole rewrite. The final checklist ("confirm there are no em dashes") became a block of grep commands that must print nothing. It starts with a grep for the em dash character itself, plus a second grep carrying the full banned-word alternation. A grep doesn't care how smart the model is. The weakest executor and the strongest one get exactly the same verdict. So the model's job shrinks from "judge your own prose honestly" to "run this command and react to its output," which even small models do well.

**Retries get an exit path.** The rewritten image loop reads: maximum 3 attempts per image on one model; after that, switch to a different image model for up to 2 more attempts; if it still fails, omit the image and record the failure in the final report. Every branch ends somewhere, and no state is left undefined.

**Cross-references get inlined.** The rules that used to live in the sibling skills were copied into this one, with a maintenance note in both files saying the duplication is deliberate and must be kept in sync. That offends my instincts as a programmer, and I did it anyway, because you can't assume any other file is loaded on the harness that runs this one. For skills, self-contained beats DRY.

**The description states its trigger.** "Use when asked to write a blog article from a topic description. Researches the topic, writes `tutorials/<slug>/index.md`, and generates verified illustration images." Trigger condition, input, output. Now the routing layer can actually route.

And here's the run against the condensed rewrite:

```bash
$ python3 skill_lint.py rewritten-skill.md

0 finding(s)

```

I also pointed it at the full production `SKILL.md` that came out of this rewrite, the one that generated this article. Same result: zero findings.

## What the linter cannot see

A clean lint run doesn't make a skill portable. It only means the skill has none of the six problems a regex can detect.

The linter can't detect a missing research phase, because it doesn't know the skill was supposed to have one. The two worst gaps in my original file were sins of omission: no research step, and no requirement that the article's code actually be executed. Omissions have no line number. The only fix I know is the audit question behind pattern two. For every capability you describe when you tell a colleague what the skill does, grep the file for it. "It researches the topic" while `research` has zero matches is a finding no regex will hand you.

The second limit is subtler. Mechanical checks create their own failure mode. Once a model knows its image will be verified against a description, a weak model under retry pressure starts optimizing against the checker. It strips concrete objects out of the prompt until nothing can fail, because nothing is depicted. My rewritten skill now carries an explicit anti-gaming rule: if your last three prompt edits each removed an idea rather than fixing how it renders, stop and change strategy. I didn't invent that rule from theory. I watched a session drift from "a cathedral stone seen through a peephole" to content-free gradient shapes, one defensive edit at a time.

Third, there's a real tension with the spec's guidance to keep a skill body under roughly 5,000 tokens. Explicit procedures, restated constraints, inlined sibling rules and few-shot examples all add length, and my rewrite roughly doubled the file. I took that trade knowingly. Progressive disclosure means the body only loads when the skill fires. A 9,000-token skill that executes correctly on a 13B-active model is worth more than a 4,000-token one that only works on the frontier. When you don't control the executor, boring and explicit beats elegant and inferred every time.

One last thing, which is either a payoff or a disclosure depending on your taste: the article you just read was produced by the rewritten skill. The grep gate ran against this file. The images above and below were generated, then described by a vision pass, then compared against the surrounding paragraphs, with the bounded retry-and-switch loop standing by. I ran the skill under a strong model this time, so I can't honestly claim "a small model wrote this." What I can claim is that every check that gated this article would give the same verdict no matter which model ran it. That's the property the whole audit was after. The recipe is written down now, so any cook can follow it.

## Appendix: the files

Save these three files in one folder to run the commands above as written.

### `skill_lint.py`

```python
#!/usr/bin/env python3
"""skill_lint.py: flag constructs in a SKILL.md that break on weak executors.

Usage: python3 skill_lint.py path/to/SKILL.md
Exits 1 if any findings, 0 if clean.
"""
import re, sys

# Tool names that only exist in one harness. A skill that says
# "use view_file" fails on every harness that calls it something else.
HARNESS_TOOLS = [
    "view_file", "str_replace_editor", "read_file", "invoke_subagent",
    "run_terminal_cmd", "codebase_search", "browser_action",
]

# Verification the harness cannot observe: a hidden reasoning channel.
HIDDEN_CHANNEL = re.compile(r"in your (thoughts|thinking|head)|silently (confirm|check)", re.I)

# Self-reported quality gates with no command to run. Weak models answer
# YES to these regardless of the actual file contents.
SELF_REPORT = re.compile(r"\b(make sure|double-check|confirm that|ensure) (that )?(everything|each|all|it)\b", re.I)

# Retry instructions. Flagged unless an exit path appears within 3 lines.
RETRY = re.compile(r"\b(retry|regenerate|try again).{0,40}\b(up to|max(imum)?)\s+\d", re.I)
EXIT_PATH = re.compile(r"\b(otherwise|if (it )?still|after that|then switch|give up|omit|fall back|report)\b", re.I)

# References to sibling skills. The other file may simply not be loaded
# on the harness that runs this one.
CROSS_REF = re.compile(r"\bsee the [\w-]+ skill\b|\brefer to the [\w-]+ skill\b", re.I)

WORKFLOW_HEADING = re.compile(r"^#+\s.*(workflow|step \d|execute in order)", re.I)

def lint(text):
    lines = text.splitlines()
    tool_pat = re.compile("|".join(re.escape(t) for t in HARNESS_TOOLS))
    findings = []

    for n, line in enumerate(lines, 1):
        if tool_pat.search(line):
            findings.append((n, "harness-specific tool name", line))
        if HIDDEN_CHANNEL.search(line):
            findings.append((n, "verification in hidden channel", line))
        if SELF_REPORT.search(line):
            findings.append((n, "self-reported check, no command", line))
        if CROSS_REF.search(line):
            findings.append((n, "cross-reference to sibling skill", line))
        if RETRY.search(line):
            window = " ".join(lines[n - 1 : n + 3])
            if not EXIT_PATH.search(window):
                findings.append((n, "retry loop with no exit path", line))

    # File-level heuristic: is there any ordered procedure at all?
    if not any(WORKFLOW_HEADING.search(l) for l in lines):
        findings.append((0, "no workflow section", "constraints only, no ordered steps"))

    # Frontmatter description should state trigger conditions.
    m = re.search(r"^description:\s*(.+)$", text, re.M)
    if m and not re.search(r"\buse when\b", m.group(1), re.I):
        findings.append((0, "description has no trigger condition", m.group(1)))

    return findings

if __name__ == "__main__":
    findings = lint(open(sys.argv[1]).read())
    for n, rule, text in findings:
        loc = f"line {n:>3}" if n else "file    "
        print(f"{loc}  [{rule}]  {text.strip()[:60]}")
    print(f"\n{len(findings)} finding(s)")
    sys.exit(1 if findings else 0)

```

### `naive-skill.md`

```markdown
---
name: blog-tutorial-writer
description: An expert technical blog tutorial writer utilizing loop engineering and autonomous verification to produce engaging, non-generic tutorials.
---

# Blog Tutorial Writer

You write engaging technical tutorials. Follow these rules:

- Writing must be "windowpane prose": invisible, grounded, highly descriptive.
- Use the 4-MAT model: Why, What, How, What If.
- Banned words: crucial, delve, tapestry, testament, vibrant, comprehensive, robust.
- Avoid em dashes entirely. Avoid the Rule of Three.
- Every article gets its own folder. The main file must be named index.md.
- Never use the word "Tutorial" in the folder name or title.
- Images must not cluster; spread them out periodically to break up text walls.
- Images must use this tag: <img src="your_image.jpg" style="max-width: 350px; float: left;">
- For prose style details, see the humanizer skill.
- For image discipline, see the my-visual-content-generation skill.

## Image verification

After generating an image, use view_file to inspect the result. Compare it
to the prompt. If it does not match, adjust the prompt and regenerate.
Retry up to 3 times per image.

## Before finishing

Output this checklist in your thoughts and confirm each item:

1. No "Tutorial" in title or folder?
2. No banned slop words or em dashes?
3. Images spaced out periodically?
4. Images using the HTML text-wrap tag?
5. 4-MAT and windowpane prose applied?

Make sure everything is perfect before you finish.

```

### `rewritten-skill.md`

```markdown
---
name: blog-tutorial-writer
description: Use when asked to write a blog article from a topic description. Researches the topic, writes tutorials/<slug>/index.md, and generates verified illustration images. Fully self-contained.
---

# Blog Tutorial Writer

Execute the steps below in order. Every rule is mandatory. Work autonomously;
if something is impossible, finish everything else and list the blockers in
your final report.

## Workflow (execute in order)

## Step 1: Research

Run 3 to 6 web searches on the topic. Verify every hard fact you plan to
state. Collect 3 to 5 source URLs to link inline.

## Step 2: Outline

Pick ONE running example the reader builds start to finish. Map the outline
onto Why, What, How, What If.

## Step 3: Draft

Write tutorials/<slug>/index.md. Prose rules, restated here because they
apply here: windowpane prose, active voice, use "I" and "we". Do not use
em dashes. Do not use these words in any inflection: crucial, delve,
tapestry, testament, vibrant.

## Step 4: Verify the code

Every code block the reader is meant to run must have been run by you. All
shown output is pasted from a real run.

## Step 5: Images

Generate ONE sample image first and verify it before generating the rest.
To verify: open the image file with whatever file-reading capability your
environment provides. If your model has no vision, send the image to a
vision model API and ask for a neutral description, then compare it against
the prompt and the adjacent article text yourself. If it fails, adjust the
prompt and regenerate. Maximum 3 attempts per image on one model; after that,
switch to a different image model for up to 2 more attempts; if it still
fails, omit the image and record the failure in your final report.

## Step 6: Mechanical checks

Run these from the article folder. Every command must print nothing.
If any prints matches, fix the file and rerun all of them.

    grep -n "—" index.md
    grep -inE '\b(crucial|delve|tapestry|testament|vibrant)\b' index.md

## Step 7: Report

Print a final report in your visible reply: word count, images generated
and verified, code blocks executed, sources used, and any failures.

```