knack
← all posts

The SKILL.md Compatibility Matrix Nobody Published

32 tools implement the Agent Skills spec by name. About six run a vanilla SKILL.md identically. The other 25 break portability in at least one specific way, and you can map every break to one of five axes.

I copied the gh-address-comments skill out of openai/skills last week, dropped it into ~/.claude/skills/gh-address-comments/, and ran Claude Code. Two of the three slash commands worked. The third one shelled out to a Python script using a path Codex expected ($AGENTS_SKILLS_ROOT) that Claude doesn't set. The skill activated. The body loaded. The script crashed. That is what SKILL.md compatibility looks like in May 2026.

This is a reference page about that gap. The headline number you see in every roundup is real: 32 tools implement the Agent Skills spec by name. The real number is closer to six or seven, meaning the count that runs a vanilla SKILL.md folder identically with no path remapping or extension-field surprise. The other 25 each break portability in at least one specific way, and you can map every break to one of five axes.

I went and read the docs for the highest-profile adopters and built the matrix below. Use it before you publish a skill anywhere.

The spec, before the deviations

A skill is a directory. Inside that directory is a file called SKILL.md, exact capitalization, no .skill.md, no skill.md lowercase. The file has YAML frontmatter and a Markdown body. The frontmatter requires two fields:

  • name: 1 to 64 characters, lowercase alphanumeric and hyphens, must match the parent directory name
  • description: 1 to 1024 characters, what the skill does and when to use it

Optional fields in the base spec: license, compatibility, metadata (free-form key-value), and allowed-tools (marked experimental, support varies). That is the whole frontmatter contract. Everything else you have seen, paths, disable-model-invocation, context: fork, agent, model, effort, hooks, argument-hint, arguments, when_to_use, user-invocable, shell, comes from one specific vendor and is silently ignored or actively rejected by another.

The directory layout is permissive. Alongside SKILL.md, agents may find scripts/, references/, and assets/ directories. None are required. The spec says one level of nesting is the recommended ceiling for cross-file references.

Progressive disclosure is the part everyone agrees on. The agent loads name and description at startup (roughly 100 tokens). It loads the full SKILL.md body only when it decides to invoke. Files in scripts/ and references/ load on demand. This is why a single repo can ship 50 skills without blowing context.

So far so portable. Now the trouble.

The five axes that break portability

Every compatibility failure I have hit traces back to one of these:

  1. Frontmatter field support. Vendor X reads disable-model-invocation, vendor Y silently ignores it, vendor Z throws.
  2. On-disk path and scope. Project skills live in .claude/skills/, or .agents/skills/, or .cursor/skills/, or .gemini/skills/, or .opencode/skills/, or .warp/skills/, or .junie/skills/, or .github/skills/. User skills add eight more roots.
  3. Invocation sigil. Claude uses /. Codex uses /skills plus $skillname mentions. Cursor uses /. Most others use /. A few autoload only and have no sigil at all.
  4. Extension fields each vendor adds. Claude adds 13 of them. Codex adds three. Cursor adds two plus a globs alias. Each one is invisible to the other vendors.
  5. Supporting-file conventions. What runs scripts/extract.py? With what interpreter? With what env vars? With what cwd?

Now the matrix.

The matrix

Green means: the agent reads this aspect of the base spec correctly, with no surprise. Yellow means: it works but with a documented quirk you have to know. Red means: it diverges in a way that breaks a vanilla skill.

Adopter Base frontmatter Project path User path Invocation sigil Scripts execute Extension fields
Claude Code Green .claude/skills/ ~/.claude/skills/ / Green 13 (yellow if you depend on them)
Codex CLI Green .agents/skills/ $HOME/.agents/skills/ /skills + $name Green 3 via agents/openai.yaml
Cursor Green .agents/skills/ or .cursor/skills/ ~/.agents/skills/ or ~/.cursor/skills/ / Green paths, disable-model-invocation, globs (legacy)
GitHub Copilot (VS Code) Green .github/skills/ (also reads .claude/skills/) n/a (org-managed) autoload + / Yellow (sandboxed) None published
Amp Green .agents/skills/ (reads .claude/skills/) ~/.config/agents/skills/ / Green mcp.json sidecar
Goose (Block) Green .agents/skills/ ~/.config/agents/skills/ (reads ~/.claude/skills/) autoload Green None
opencode Green .opencode/skills/ (reads .claude/skills/, .agents/skills/) ~/.config/opencode/skills/ + Claude/agents aliases autoload + tool Green Agent-level permissions in opencode.json
Gemini CLI Green .gemini/skills/ (alias: .agents/skills/) ~/.gemini/skills/ autoload Green --path install flag, one-deep limit
Junie (JetBrains) Green .junie/skills/ ~/.junie/skills/ (Windows: %USERPROFILE%\.junie\skills\) autoload + / Green None documented
Zed Green .agents/skills/ ~/.agents/skills/ autoload Yellow (asks before edits to skills dir) None
Warp Green .warp/skills/ (also reads 7 other roots) ~/.warp/skills/ autoload + / Green None
VS Code (native) Green inherits Copilot config inherits Copilot config UI picker Yellow None
Aider Yellow n/a (uses conventions doc) n/a manual paste n/a None
Factory Green .factory/skills/ ~/.factory/skills/ autoload Green None published
Jules (Google) Green inherits Gemini path inherits Gemini path autoload Yellow None
Devin / Windsurf Yellow .agents/skills/ (Cognition) n/a (cloud) autoload Yellow (cloud sandbox) None
Kilo Code Green .agents/skills/ ~/.agents/skills/ autoload Green None
RooCode Green .agents/skills/ (reads .claude/skills/) ~/.agents/skills/ / Green None
Augment Green .augment/skills/ (reads .claude/skills/) ~/.augment/skills/ / Green None
Phoenix Yellow cloud-only cloud-only autoload Red (no shell) None

That is 20 of the 32. The remainder (Semgrep, Ona, UiPath Autopilot, Coded Agents, and a handful of niche IDEs) either bundle a fixed set of vendor skills with no user import path, or they implement an autoloader so thin it isn't worth a row.

The pattern is obvious once you see it. Almost everyone agrees on the base frontmatter. Almost no one agrees on the path.

Path-driven failures, in detail

The eleven roots I have personally seen a skill live in: .claude/skills/, .agents/skills/, .cursor/skills/, .codex/skills/, .gemini/skills/, .opencode/skills/, .warp/skills/, .junie/skills/, .github/skills/, .factory/skills/, .augment/skills/. The user-scope versions of all of these. That is 22 directories an agent might read.

Most adopters mitigate this by reading several of them. Cursor reads all four of its own roots plus the Claude and Codex roots, project and user. Warp reads nine roots. opencode reads the .opencode/, .claude/, and .agents/ roots in both scopes. Gemini CLI treats .agents/skills/ as an alias for .gemini/skills/.

The convergence point is .agents/skills/. If you put your skill there, eight or nine adopters will find it. If you put it in .claude/skills/, Claude finds it, plus the four or five competitors that read the Claude path for compat. The base spec does not mandate either. This is the single biggest portability decision you make.

A practical rule: write to .agents/skills/ for new skills, and let Claude's auto-discovery pick it up via project root walking (Claude reads .claude/skills/ but most teams symlink or run knack pull once into both). User-global skills are messier because Junie wants .junie/, Gemini wants .gemini/, and Warp wants .warp/. There is no universal user root.

Frontmatter divergence: the field-by-field reality

The base spec has six frontmatter fields. Claude Code's docs list 13 additional ones. Here is what happens to them in the wild.

disable-model-invocation (Claude, Cursor): tells the agent to never auto-invoke. The user has to type the slash command. Claude and Cursor both honor it. Codex, Gemini CLI, Amp, opencode, Warp, Junie, Goose all silently ignore it. Your "user only" deploy skill auto-invokes on Codex. This has bitten teams I know.

context: fork and agent: Explore (Claude only): forks the skill into a subagent context. Unique to Claude Code. Nobody else parses these fields. A skill that relies on context: fork for context isolation will run inline everywhere else, polluting the main context window. Not a crash. Just a quiet degradation.

allowed-tools (spec, experimental): Claude honors it for pre-approving tool use. Cursor mostly ignores it, asks anyway. Codex routes tool grants through agents/openai.yaml. opencode uses its own permission block in opencode.json and ignores the frontmatter version. The experimental flag in the spec was earned.

paths and globs (Cursor, Claude): scope skill activation to file patterns. Cursor accepts both. Claude accepts paths. Codex ignores both. If you ship a "react-component-review" skill scoped to **/*.tsx, it activates on every prompt in Codex, regardless of what file is open.

when_to_use (Claude): appended to description for trigger matching. Other agents ignore the field, which means your trigger phrases vanish. Fold them into description if you want portability.

argument-hint and arguments (Claude): autocomplete and $name substitution. Codex has a different substitution model. Cursor has none. If your skill body has Fix issue $issue in $component, only Claude renders that. Everyone else passes through the literal string.

model, effort (Claude): override the inference model and effort tier. Universally ignored elsewhere.

hooks (Claude): inline shell hooks that fire on skill lifecycle events. Zed and Amp have their own hooks systems and do not parse Claude's syntax. Everyone else: no hooks.

mcp.json sidecar (Amp): Amp lets you ship an mcp.json next to SKILL.md to declare MCP servers the skill needs. No other vendor reads this file. If your skill depends on a Postgres MCP server declared in mcp.json, only Amp wires it up. Claude users will see the skill activate and then fail with "tool not available".

agents/openai.yaml sidecar (Codex): the inverse problem. Codex looks for a sibling YAML that declares interface, policy, and dependencies. No other vendor reads it.

So the realistic frontmatter contract for a portable skill is: name, description, optionally license and metadata. That is it. Anything else makes you Claude-rich or Codex-rich, and you are choosing your audience.

Scripts: what actually runs

The spec says scripts/ is optional and "supported languages depend on the agent implementation." That sentence is doing a lot of work.

Claude Code runs shell commands inline using !`command` syntax inside SKILL.md itself, and runs scripts/foo.py if the body references it and Bash plus Python are on PATH. Default shell is bash. Override to PowerShell with shell: powershell in frontmatter. On Windows without WSL, the default fails silently if you forgot to override.

Codex runs scripts but expects them to declare dependencies inside agents/openai.yaml. Without that declaration, you get "missing dependency" prompts even when the binary is on PATH.

Cursor runs scripts the same way Claude does, minus the shell: override. Bash assumed. Windows-native Cursor users hit this constantly.

GitHub Copilot sandbox runs scripts in a constrained environment, with network access gated by the org policy. Long-running scripts get killed. Filesystem access is scoped to the workspace.

Phoenix is the outlier: cloud-only, no shell. Skills that depend on running anything in scripts/ are dead on arrival.

Warp, Amp, opencode, Junie, Goose, Gemini CLI, Zed all run scripts more or less identically to Claude, with platform-specific quirks (Junie on Windows uses %USERPROFILE%, Goose ships its own shell wrapper).

The lowest-common-denominator skill

The skill that runs identically on every adopter looks like this. Directory name matches frontmatter name. File is SKILL.md exact case. Frontmatter is two fields. Body is Markdown. Any scripts/ are POSIX bash plus pure Python with a shebang. No reliance on paths, disable-model-invocation, or any extension.

my-skill/
  SKILL.md
  scripts/
    do-thing.sh
  references/
    rationale.md
---
name: my-skill
description: One sentence about what it does. One sentence about when to use it.
---

If you want broader trigger matching, fold "when to use" language directly into description. Stay under 1024 characters. Reference files in the body as scripts/do-thing.sh and references/rationale.md, relative paths only.

That skill works in Claude Code, Codex CLI, Cursor, Amp, opencode, Goose, Gemini CLI, Junie, Warp, Zed, GitHub Copilot, Factory, Kilo, RooCode, and Augment. Fifteen targets, one folder.

The Claude-rich (or Codex-rich) skill, kept portable

Sometimes you want the extensions. You want context: fork for context isolation on Claude. You want agents/openai.yaml for tool grants on Codex. You can have both without breaking the base. The vendors silently ignore fields they don't recognize, so the strategy is:

Put everything portable in SKILL.md frontmatter and body. Put Claude-specific behavior in additional frontmatter keys (context, agent, allowed-tools, hooks). Put Codex-specific behavior in a sibling agents/openai.yaml. Put Amp-specific MCP declarations in a sibling mcp.json.

The body of SKILL.md should be written so that a vendor without your fancy extensions still produces a correct result. Treat the extensions as optimizations a vendor can ignore. If your skill only works with context: fork, you have a Claude product, not a skill.

Authoring against the lowest common denominator and then layering vendor sidecars is how Knack outputs work in all 32. The generator writes a minimal valid SKILL.md, adds the supporting scripts/ and references/, and only emits extension fields when the user explicitly asks for vendor-specific behavior. The default is portable.

Edge cases worth knowing about

The capitalization trap. SKILL.md is uppercase. skill.md is invisible. Skill.md is invisible. The matcher is case-sensitive on Linux and macOS. Windows filesystems lie about case but the matchers still check. Several adopters list this as their top support ticket.

The directory-name-must-match trap. Frontmatter name: pdf-processing means the directory must be pdf-processing/. Rename one without the other and validation fails on Claude and Cursor. Codex and Gemini are lenient. opencode warns. Goose silently de-duplicates.

The one-level-deep trap. Gemini CLI explicitly does not descend past one directory inside the skills root. So .gemini/skills/myteam/code-review/SKILL.md is invisible. Claude and Cursor walk subdirectories. Warp walks recursively. opencode walks one level. If your team groups skills by domain inside subfolders, half your tools will not find them.

The auto-invocation trap. Skills that mutate state (deploy-prod, drop-tables) should set disable-model-invocation: true on Claude and Cursor. Everywhere else, the field is ignored. If you publish a destructive skill, write the safety check into the body and refuse to act without an explicit user confirmation message. Do not rely on the frontmatter.

The compatibility field is advisory only. Setting compatibility: Requires Python 3.14+ and uv does not stop a vendor from invoking the skill on a machine without uv. The field is documentation that nothing enforces.

Frontmatter parsers diverge on YAML quirks. A multi-line description using >- works on Claude, Cursor, Codex, opencode. It breaks on at least two adopters whose YAML parser predates the YAML 1.2 spec. Keep description on a single line. Use \n if you need a break.

The honest summary

The 32-adopter number is true and useful. The "drop your SKILL.md anywhere and it runs" claim is not. About six adopters run a vanilla skill identically: Claude Code, Codex CLI, Cursor, Amp, Goose, opencode. Add Gemini CLI, Junie, Warp, Zed, GitHub Copilot, Factory, Kilo, RooCode, and Augment if you accept yellow-flag quirks like one-level-deep, sandboxed scripts, or Windows path differences. The remaining adopters are either cloud-only walled gardens or fixed-set bundles where user imports don't apply.

If you want portability, write to the base spec, drop into .agents/skills/, keep your scripts POSIX, fold trigger phrases into description, and reserve extension fields for vendor sidecars. If you want Claude's context: fork or Codex's policy block, ship them as additions, not requirements.

The matrix above is dated 2026-05-21. Recheck it before December. Three of the rows will have moved.