Skip to content
Skip to main content
Claude, OpenAI, Cursor and Gemini logo tiles on a gold network background, representing one SKILL.md shared across AI coding agents
9 min readBy Carlos Aragon

Portable SKILL.md: One Skill for Every AI Agent

A skill is portable when its frontmatter carries nothing but name and description, and its steps never name a specific agent's tools, paths, or slash commands. Put that folder in .agents/skills/ and the same file loads unmodified in Claude Code, Codex, Cursor, Gemini CLI, VS Code and dozens of other clients. Everything that breaks portability is something you added on top of that core — and I can name the four culprits, because I found them in my own library.

The Portable Core Is Two Fields and a Folder

Anthropic released Agent Skills as an open standard, and the ecosystem picked it up fast enough that arguing about formats is now pointless. A skill is a folder. The folder has a SKILL.md with YAML frontmatter and Markdown instructions. It can bundle scripts/, references/, and assets/alongside. That's the whole spec, and it's deliberately small.

Two frontmatter fields are required: name and description. Everything else — version, author, license, platforms, allowed-tools, model — is an extension some clients read and others quietly drop. That's fine. It only becomes a problem when a skill's behavior depends on one of them being honored.

I run 174 skills across my agent stack. When I audited the frontmatter of every one of them for this post, name and description appear in all 174; allowed-tools appears in exactly two.That ratio is not an accident — it's what a library looks like when you write for the standard instead of for one host.

Where Each Agent Actually Looks for Skills

The format is shared. The discovery path is not — every client scans its own directory, and that's the only real friction left. Here's the map:

ClientProject scopeGlobal scope
Claude Code.claude/skills/~/.claude/skills/
Codex.codex/skills/~/.codex/skills/
Cursor.cursor/skills/ plus the Claude and Codex folders~/.cursor/skills/
Cross-client convention.agents/skills/~/.agents/skills/

Write to .agents/skills/ and symlink the rest.More clients read that path every month, and the ones that don't yet still follow a symlink like any other directory. One source of truth, no copies drifting out of sync, no “which version of this skill did I fix?” on a Tuesday afternoon.

The Four Things That Break Portability

I ran a grep over all 174 of my SKILL.md files looking for each of these. The results were not what I expected, and the one I was least worried about turned out to be the actual leak.

1. Naming one harness's tools inside a step

“Use the Read tool to open the config” is a dead instruction in any agent that doesn't have a tool called Read. Write the work, not the call: read the config file. The agent already knows which of its tools does that. This is the failure mode everyone warns about, and it's the easiest to avoid — zero of my 174 skills had it, because it reads as bad writing even before you think about portability.

2. Hardcoded absolute paths

Nine of my 174 skills hardcode /Users/carlos. Every one of those breaks the moment the skill runs on another machine, in a container, or under a different user — and it breaks silently, because the agent will happily report that a path doesn't exist and improvise something else. This was my leak. If you audit one thing after reading this, audit for your own home directory.

The fix is boring: relative paths from the repo root, an environment variable, or a first step that asks where the thing lives. Boring is the point.

3. Treating extension frontmatter as load-bearing

allowed-tools, model, and disable-model-invocationare useful where they're supported and invisible where they're not. Use them as hints, never as guarantees. A skill that only behaves correctly because one client restricted its tools is a skill that misbehaves everywhere else — and you won't find out until it does.

4. Bundled scripts that assume an environment

Shipping a scripts/ folder is the best part of the format and the fastest way to break it. A Python helper that assumes a specific interpreter, a CLI that assumes a global install, a step that assumes credentials are already in the shell — all of it works on your machine and nowhere else. Declare prerequisites in the body, and make the first step of the procedure check for them rather than assume.

The one-line test:

Could a competent contractor follow this skill on a laptop you've never touched? If any step assumes your machine, your tool names, or your shell, it isn't portable — it's a note to yourself with YAML on top.

The Description Is the Retrieval Layer — Edit It Like One

Skills load through progressive disclosure. At startup the client reads only each skill's name and description; the full body enters context only when a request matches. That single design decision is why a big library is cheap, and the numbers from mine make it concrete:

  • 174 SKILL.md files, about 1.7 MB of instructions in total — roughly 430,000 tokens if every one loaded at session start.
  • The names and descriptions together are about 21,000 characters — a bit over 5,000 tokens.
  • That's near 1% of the full weight sitting in context, and the other 99% only shows up when it's actually needed.

Compare that to a stack of always-on tool definitions, where every connected server pays rent in your context window from the first message whether it gets used or not. I broke that math down in Claude Skills vs MCP, and the follow-on cost in what MCP servers really cost per session. Skills and MCP aren't rivals — MCP gives an agent access, skills give it judgment — but only one of them scales to 174 entries.

The consequence is that your description is the only thing competing for the agent's attention. Mine average 93 characters. Write them as trigger conditions containing the nouns a user will actually type, not as summaries of the body:

Weak: “Helps with deployments.”

Strong: “Use when deploying a Next.js app to Vercel, promoting a preview to production, or rolling back a bad release.”

The strong one fires. The weak one sits in the library forever, and you conclude skills don't work.

The Layout I Run: One Source, Symlinks Everywhere Else

Nothing clever here, which is why it has survived three agent migrations:

~/.agents/skills/                 # the only real copy
  deploy-nextjs-vercel/
    SKILL.md                      # name + description + steps
    scripts/verify-deploy.sh
  review-pull-request/
    SKILL.md

~/.claude/skills  -> ~/.agents/skills
~/.codex/skills   -> ~/.agents/skills
~/.cursor/skills  -> ~/.agents/skills

Repo-scoped skills follow the same shape one level down: .agents/skills/ committed to the project, symlinks for the clients that need them, and the whole thing reviewed in pull requests like any other code. That last part matters more than the directory layout — a skill is executable documentation, and the moment it stops being reviewed it starts lying about how the system works.

If you're distributing skills to a team rather than to yourself, the packaging story is different and worth its own read: running a private plugin marketplace covers versioning and rollout.

How to Verify a Skill Is Actually Portable

Three agents, three checks, about five minutes. Don't skip the third one — a skill that loads and then stalls halfway through is the expensive failure, because you only notice mid-task.

  1. 1Does it list? Ask each client to show its available skills. If yours is missing, it's a discovery problem — wrong directory, or malformed frontmatter that failed to parse.
  2. 2Does it fire? Give the client the phrase a real user would type, without naming the skill. If it doesn't activate, the description isn't a trigger condition yet. Rewrite it and try again.
  3. 3Does it finish? Let it run the whole procedure. Watch for the step where it asks you something the skill should have told it, or reaches for a path that doesn't exist. That's your portability bug, and it's always in the second half.

The official Agent Skills specification is short enough to read in one sitting, and the Claude Code skills documentation covers the client-specific bits worth knowing.

When a Skill Should Not Be Portable

Portability is a means, not a virtue. Some of my most-used skills are deliberately locked to one host: how to debug that harness's slash commands, how to author its plugins, which config file it reloads and which one needs a restart. Making those tool-agnostic would mean deleting the specifics that make them useful.

The line I use: if the skill describes work, make it portable. If it describes a runtime, name the runtime. Keep the second kind in that client's own directory and stop feeling bad about it. The same split shows up when you're deciding what to delegate at all — I went through that trade-off in skills vs subagents, and again in Agent Teams vs subagents.

The Short Version

  • A portable skill is a folder with a SKILL.md whose frontmatter is name plus description. Everything past those two fields is optional, and nothing should depend on it.
  • Keep the single copy in .agents/skills/ and symlink .claude/skills, .codex/skills, and .cursor/skills at it. One file, every client, no drift.
  • Write steps as work, not as tool calls. "Run the test suite" travels; "use the Bash tool" does not.
  • Audit for your own home directory. Nine of my 174 skills hardcoded /Users/carlos, and that silent failure was the real portability leak — not the one everyone writes about.
  • The description is the retrieval layer. Mine average 93 characters and cost about 1% of what the full library would; that's the entire reason 174 skills is a sane number to keep installed.
  • Skills that document a specific runtime should name it and stay put. Portability is for procedures, not for internals.

Want a Skill Library Your Whole Team Can Actually Use?

I build production agent systems on Claude, the Claude Agent SDK, and n8n — including the skill layer that turns tribal knowledge into procedures every agent on the team can run. If your automation lives in one person's head or one vendor's format, let's fix that.

Related Posts

AI Agents

Claude Code Agent Teams vs Subagents: When to Use Each

Agent Teams shares a task list and git worktrees between sessions; subagents delegate and report back. They solve different coordination problems — the decision framework I use, and the merge trap Agent Teams hides.

AI Agents

Claude Code Sandbox: What It Actually Blocks

The Claude Code sandbox runs Bash and every process it spawns inside an OS-enforced boundary — Seatbelt on macOS, bubblewrap on Linux and WSL2. Writes are locked to your working directory; network egress is denied until you allow a domain. Reads are not restricted, so a sandboxed command can still open ~/.ssh and ~/.aws/credentials on a default setup. That asymmetry decides how you configure it: deny the credentials explicitly, keep allowedDomains narrow with strictAllowlist on, and set allowUnsandboxedCommands to false before anything runs unattended.

AI Agents

MCP Server Security: How to Stop Tool Poisoning

Tool poisoning is when an MCP server hides instructions inside its own tool descriptions — text your agent reads as commands and you almost never see. The model obeys it because, inside the context window, a description and a system prompt are the same kind of thing, which is why no system prompt fixes this. The four controls that hold are structural: approve individual tools instead of whole servers, pin exact versions and diff the tool descriptions in CI so a rug pull is a failed build, keep secrets out of the model's context entirely, and run local servers in a container with no network access.