A local SKILL.md folder is the fastest loop for authoring a skill and the only surface the API cannot see — nothing there is reachable from a Messages API call until you register it.
Why you'd careThe problem it solves
You wrote a skill, restarted Claude Code three times, and it has never fired. Nothing errored. The session started, the folder was there, and the model simply chose not to use it — which looks identical to the folder not being found at all. This is the normal first experience with local skills, and it is usually not a harness bug. The local surface has no registration step, no ID, and no version number, so there is nothing to inspect when it goes wrong: no skill_id to fetch, no version list, no server-side error to read. What the harness hands the model is one name and a short description. If that description does not read like the task the user just asked for, the skill sits on disk untouched, and you get no signal at all.
ConceptWhat it is
A Claude Code skill is a directory. Its one required member is a file named SKILL.md, whose YAML frontmatter normally carries a name and a description — though Claude Code itself requires neither, defaulting name to the directory name and description to the first non-empty line of the body. Everything below the frontmatter is Markdown instructions, and everything else in the directory — scripts, reference documents, templates — is just files those instructions can point at.
Two properties make it a skill rather than a README. First, the harness enumerates skill directories at session start and puts only the frontmatter into context, so a hundred skills cost roughly a hundred lines rather than a hundred documents. Second, the model can pull the body in on demand. That two-stage load is progressive disclosure, and it is the whole reason the description carries so much weight: it is not documentation, it is the routing key.
The boundary worth holding onto is resolution. A local skill is resolved off the filesystem by the process you are running. It has no server-side existence. The Messages API cannot see it, a Managed Agent cannot reference it, and no skill_id exists for it anywhere. Which directories get scanned depends on scope — a personal directory, a directory inside the repo you are working in, a plugin that ships them — and that scoping is the only "where" a local skill has. Registering the same folder through the Skills API creates a separate object that happens to share content; it does not link the two, and editing the folder afterwards does not update the registered version.
How it worksThe mechanics
The artefact is a directory with a SKILL.md at its root:
release-notes/
├── SKILL.md
├── references/
│ └── changelog-format.md
└── scripts/
└── collect_merged_prs.shAnd the file itself, frontmatter first:
---
name: release-notes
description: Draft release notes from merged PRs for a tagged version.
Use when the user asks to cut, draft, or write release notes,
or mentions a version tag like v2.4.
---
# Release notes
1. Run `scripts/collect_merged_prs.sh <tag>` to gather merged PRs
since the previous tag.
2. Group them using the categories in `references/changelog-format.md`.
3. Omit dependency bumps unless they change behaviour.name and description are the two keys that are load-bearing everywhere. Individual surfaces accept further optional keys, and those differ by surface and move over time — check the frontmatter reference for the surface you are targeting rather than copying a key you saw in someone's repo.
The load happens in three steps. At session start the harness reads each skill's frontmatter and injects only the name and description into context. When the model judges that a description matches the task, it invokes the skill by name and the body of SKILL.md is loaded. From there the body is ordinary instructions: references to bundled files are read with the normal file tools, and scripts are run with the normal execution tool. There is no special skill runtime.
One timing consequence is gentler than it looks. Claude Code watches its skill directories, so adding, editing or removing a skill, description included, is picked up within the current session without a restart; only a top-level skills directory that did not exist at startup needs one. What does not refresh is a body the model has already loaded: once invoked, the rendered SKILL.md stays in the conversation and is not re-read on later turns, so a body edit lands on the next invocation. If you are tuning a description, still run each attempt in a fresh session, because leftover context from earlier attempts masks what the description alone does.
At a glanceSee it
How a local skill folder reaches the model, and where a vague description silently ends the path.
Where it runsSurfaces and availability
| Surface | Status | Notes |
|---|---|---|
| Claude Code | Yes | The native surface. Seven locations — enterprise, personal ~/.claude/skills/, project .claude/skills/, nested <subdir>/.claude/skills/, the .claude/skills/ of an --add-dir directory, plugin, and skills synced from your claude.ai account — where enterprise overrides personal and personal overrides project. Claude Code watches these directories rather than reading them once: adding, editing or removing a skill takes effect within the current session, and only a top-level skills directory that did not exist at startup requires a restart. Claude invokes a skill when the request matches its description, or you invoke it directly as /skill-name. Confirmed on Extend Claude with skills (code.claude.com/docs/en/skills). |
| Claude API / Messages API | No | The API has no view of your filesystem. Skills on this surface are named through container.skills by skill_id, alongside the code-execution tool, and must exist server-side first. Confirmed on Agent Skills → Where Skills work → Claude API (platform.claude.com/docs/en/agents-and-tools/agent-skills/overview). |
| Managed Agents | No | An agent's skills array takes a skill_id (plus type and optional version), not a path. A local folder has no ID. Confirmed on Managed Agents → Skills (platform.claude.com/docs/en/managed-agents/skills). |
| Claude Desktop or claude.ai | No | Now confirmable. claude.ai has its own custom-Skills feature, but Skills are uploaded as zip files through Settings > Features, not read from local disk, and Anthropic states plainly that “Claude Code Skills are filesystem-based and separate from both claude.ai and API” (Agent Skills → Limitations and constraints → Cross-surface availability). Claude Code's own docs add that Cowork and cloud sessions “don't read ~/.claude/skills/ on your machine” and instead load the skills enabled for your claude.ai account, managed from Customize in the Desktop app sidebar (code.claude.com/docs/en/skills). |
| Agent SDK | Yes | Now confirmable, and the option names are documented. Skills are filesystem artefacts loaded from locations governed by settingSources (TypeScript) / setting_sources (Python) — include 'user' or 'project' or discovery is off. The separate skills option filters what is enabled ("all", a name list, or []). Resolution covers ~/.claude/skills/, <cwd>/.claude/skills/, and .claude/skills/ in any parent up to the repository root; the plugins option loads skills from an arbitrary path. Source: Agent Skills in the SDK (code.claude.com/docs/en/agent-sdk/skills). |
| Claude Platform on AWS | No | An API surface, not a local harness — it uses “the same container.skills parameter as the Claude API” (platform.claude.com/docs/en/build-with-claude/claude-platform-on-aws). Same reason as the Messages API row. |
| Amazon Bedrock | No | Read this as “not a harness”, not “skills are absent”. The API-side Agent Skills feature is documented as “Not available (requires code execution)” on Bedrock, and the anthropic-beta header is not supported there at all. But Claude Code pointed at Bedrock with CLAUDE_CODE_USE_BEDROCK=1 still reads local SKILL.md folders normally — skills are listed among the features that “work on every provider” (code.claude.com/docs/en/feature-availability). |
| Google Vertex AI | No | Same split. Agent Skills appear under “Features not supported” for the API surface (platform.claude.com/docs/en/build-with-claude/claude-on-vertex-ai), yet Claude Code's local folders work with CLAUDE_CODE_USE_VERTEX=1 like any other provider. |
| Microsoft Foundry | No | An API surface. Foundry carries the Messages API skills path at beta — but only on a Hosted on Anthropic deployment; on Hosted on Azure, Agent Skills return 400 Bad Request by design. Local folders are unaffected: Claude Code with CLAUDE_CODE_USE_FOUNDRY=1 reads them normally. |
| OpenAI Codex CLI, Google Antigravity CLI | No | No — but not for the reason usually given. A cross-vendor standard does exist: Claude Code's docs state it “follows the Agent Skills open standard, which works across multiple AI tools”, and both Codex and Antigravity use the same SKILL.md file with name and description frontmatter. What does not transfer is the location: Codex scans .agents/skills from the working directory up to the repository root (developers.openai.com/codex/skills), and Antigravity uses <project>/.agents/skills/ plus ~/.gemini/config/skills/ (antigravity.google/docs/skills). Neither reads .claude/skills/. Moving a skill across is a copy, not a config change. |
Read the No rows as “different object model”, not “not shipped yet”. Every API surface names skills by ID; only a harness with a filesystem names them by path. That is the practical split: draft and iterate locally where the loop is a file save, then register the folder when something other than your machine needs to run it. Two things follow that are easy to get backwards. First, the local mechanism is provider-independent — pointing Claude Code or the Agent SDK at Bedrock, Vertex or Foundry does not disable your SKILL.md folders, even though those platforms lack the API-side feature; the cloud rows above are about container.skills, not about your disk. Second, the file format now travels further than the folder does: Codex and Antigravity read the same SKILL.md shape from .agents/skills/, so portability is a question of directory layout rather than format. Treating local and registered copies as one artefact that syncs is still the mistake.
ExampleIn the real world
A platform team wants Claude Code to draft release notes in their house format. The first version of SKILL.md has description: Helps with release stuff for our repos. The body is excellent — three hundred lines of formatting rules and a script that walks merged PRs.
It never fires. The engineer types "cut release notes for v2.4" and the model reads the changelog by hand, badly. Nothing in the transcript mentions the skill, because the model never saw the body: all it had was one vague line about "release stuff", competing with eleven other skills, and it did not connect that line to the request.
The fix is one line, not one paragraph. The description becomes: Draft release notes from merged PRs for a tagged version. Use when the user asks to cut, draft, or write release notes, or mentions a version tag like v2.4. It now names the artefact, the trigger verbs, and the shape of the input.
Next session, the same request fires it. The body loads, the model runs scripts/collect_merged_prs.sh v2.4, groups the output using references/changelog-format.md, and drops dependency bumps because the body says to. Nothing about the body changed. Only the routing key did.
Not thisWhat it is often confused with
- Not a toola tool is a callable with a JSON input schema that the harness executes and returns a result for. A skill is instructions the model reads. A skill can tell the model which tools to use and in what order; it is not itself invocable with arguments.
- Not an MCP serverMCP is a wire protocol between the harness and a separate running process that serves tools and resources. A skill is inert files on disk: no process, no transport, no handshake, nothing to be up or down.
- Not a system prompta system prompt is in context on every turn and applies unconditionally. A skill's body is absent until the model selects it, and skills you never use cost roughly one line each. That conditionality is the entire point.
- Not a subagenta subagent gets its own context window and its own turn loop, and reports a result back. A skill loads into the context you are already in, and the same model keeps working.
- Not a registered API skilla local folder has no
skill_idand cannot be named bycontainer.skillsor an agent'sskillsarray. Registering it creates a second, independent object; editing the folder afterwards does not update it.
LimitsWhen not to reach for it
- The behaviour must apply to every turn.If it should always be true — a coding convention, a tone rule, a hard constraint — put it in the system prompt or the project instructions file. Conditional loading is a liability when the condition is "always".
- It has to run inside a stateless API request.A local folder is unreachable from
/v1/messages. Register it through the Skills API and name it incontainer.skills, or attach it to a Managed Agent. - What you actually need is a deterministic action with typed arguments.If the failure mode you fear is the model getting the call shape wrong, you want a tool or an MCP server with a schema, not prose the model may paraphrase.
- Everyone on the team needs the same version.A personal-scope folder on one laptop is not distribution. Move it to project scope in the repo, or package it as a plugin.
- The content is a corpus, not a procedure.Thousands of documents that must be searched at query time are a retrieval problem. A skill is a fixed set of instructions and a handful of bundled files.
Verified 2026-09-12. Moves on a scale of months. Re-check before you depend on it. Provider: Anthropic.