Skillforge Field notes on shipping with AI tools

Claude Cowork Skills: Port Your Claude Code SKILL.md Files

claude-codeskillscowork

If you have a folder of Claude Code skills in ~/.claude/skills/ and you open Claude Cowork expecting to see them, you will see none of them. That is not a bug. Cowork does not read the Claude Code CLI's ~/.claude directory at all. It loads the skills enabled for your claude.ai account, synced at the start of each session, and nothing else.

The short version, before the detail:

  • Cowork skills come from your claude.ai account, managed under Customize > Skills. Local files on your machine are invisible to it. The same is true of Claude Code cloud sessions and routines.
  • The format is the same SKILL.md, because both follow the Agent Skills open standard. What differs is what the upload accepts: six frontmatter fields, a 200-character description, and no shell injection in the body.
  • Porting is a ZIP upload: folder named after the skill, SKILL.md inside, zipped with the folder as the root.
  • The reverse direction exists too: one environment variable pulls your claude.ai skills down into ~/.claude/skills/synced/ for local Claude Code sessions.

Verified on 2026-09-03 against the Cowork overview and skills pages at claude.com/docs, the Claude Code skills reference at code.claude.com, and the Help Center articles on creating and using skills. Where the sources disagree with each other, this article says so.

Why your local skills are missing

Cowork is Anthropic's agentic workspace for knowledge work: the same architecture as Claude Code, with no terminal, available on paid plans (Pro, Max, Team, Enterprise) in Claude Desktop for macOS and Windows, on the web, and on mobile. Since it runs sessions in the cloud, on Anthropic's servers rather than in a shell on your laptop, your home directory is simply not where the session lives.

The Claude Code docs spell out the consequence in one paragraph: Cowork sessions and cloud sessions, including routines, do not read ~/.claude/skills/ on your machine. Both interactive and scheduled Cowork sessions load the skills enabled for your claude.ai account, synced at session start. If a skill exists only in ~/.claude/skills/ and a routine invokes it, Claude Code reports that the skill was not found.

The one exception worth knowing: desktop scheduled tasks run locally on your machine and load skills from the same places any local session does. So a task scheduled from the desktop app can see ~/.claude/skills/, and a Cowork task cannot.

If you want the same skill in both places, you have two honest options. Upload it to your account, which is what the rest of this article covers, or package it as a plugin, which Cowork also installs. We cover the plugin route near the end.

What survives the trip

Claude Code skills and claude.ai skills share the Agent Skills specification. Claude Code then extends that spec with fields of its own: disable-model-invocation, user-invocable, context: fork, paths, hooks, argument-hint, arguments, model, effort, and more. Every one of those extensions is Claude Code only.

The Claude Code reference publishes the exact list the upload path accepts:

Distribution pathFrontmatter fields you can use
Claude Code skills at any level, including plugin skillsEvery field in the Claude Code frontmatter table
claude.ai skill uploads, the Skills API, and packaging with package_skill.pyname, description, license, compatibility, metadata, allowed-tools

It is a hard error, not a warning. The documented message when you include anything else:

Unexpected key(s) in SKILL.md frontmatter: argument-hint. Allowed properties are: allowed-tools, compatibility, description, license, metadata, name

Three more constraints apply on upload, and the middle one is the one that bites:

  • name is lowercase letters, digits, and hyphens, 64 characters maximum, and must match the folder name. In Claude Code, name is only a display label and the command comes from the directory, so a mismatch that Claude Code never noticed becomes an upload failure.
  • description is capped at 200 characters on claude.ai. The spec allows 1,024 and Claude Code truncates at 1,536 combined with when_to_use, so a description that has worked in Claude Code for months can be too long here. When we ran the check below against our own 25-skill pack, four descriptions came back between 206 and 227 characters. Nothing had ever complained. We trimmed them.
  • The body cannot run shell commands. Claude Code's dynamic context injection, the !`git diff HEAD` syntax that inlines command output before Claude reads the skill, is a Claude Code feature. In a Cowork session, Claude Code replaces every ! command line with a placeholder. The @ file references and the ${CLAUDE_PROJECT_DIR} and ${CLAUDE_SESSION_ID} substitutions are likewise not honored outside Claude Code. Write the instruction as prose ("run git diff and read the output") instead.

One discrepancy between Anthropic's own pages, flagged rather than resolved: the claude.com how-to page shows a dependencies: frontmatter field for declaring Python or npm packages, while the Claude Code reference's allowed-key list for uploads does not include it. We have not tested an upload with dependencies set. If you need packages, list them in the body as well, so the skill still works if the field is rejected.

Scripts themselves do travel. Skills can bundle Python, Node, or Bash under scripts/, and the Help Center states that Claude and Claude Code can install packages from PyPI and npm when loading a skill. The API's code execution tool is the exception, where everything must be preinstalled.

Port a skill in five steps

Take a skill that lives at ~/.claude/skills/writing-changelogs/SKILL.md.

1. Strip the frontmatter to the six fields. Keep name and description. Delete disable-model-invocation, argument-hint, context, paths, and anything else Claude Code specific. allowed-tools is permitted, but it grants Claude Code tools by name, so it is mostly noise in Cowork.

2. Make name match the folder and keep it under 64 characters. If your SKILL.md has no name field, add one that equals the directory name.

3. Cut the description to 200 characters without losing the trigger phrases. The description is the only thing Claude reads to decide whether to load the skill, so keep the "use when" clauses and cut the elaboration.

4. Remove shell injection from the body. Search for lines starting with !` and for `! fenced blocks. Replace each with a plain instruction to run the command.

5. Zip it with the folder as the root and upload. The correct shape, straight from the docs:

writing-changelogs.zip
└── writing-changelogs/
    ├── SKILL.md
    └── scripts/

Files sitting directly at the ZIP root fail. Then in Claude (web, desktop, or Cowork) open Customize > Skills, click the + button, choose Create skill, then Upload a skill, and pick the ZIP. The skill appears in your list with a toggle. Cowork picks it up at the start of the next session.

Skills require code execution to be enabled. On Free, Pro, and Max that is Settings > Capabilities > Code execution and file creation. On Team and Enterprise an owner enables both code execution and skills under Organization settings > Skills. If the Skills tab is missing or greyed out, that setting is the first thing to check.

A note on the file name: the Help Center writes it skill.md, while the claude.com how-to, the Claude Code docs, the Agent Skills spec, and Anthropic's own example repository all use SKILL.md. Use the uppercase form. It is the one every example ships with.

A pre-flight check you can run

Here is the check we now run before uploading anything. It reads each skill folder and reports what the upload would reject. Save it as check-skill.js:

// check-skill.js: will this skill folder upload to claude.ai / Cowork?
// Usage: node check-skill.js path/to/skill-folder [more folders...]
const fs = require('fs');
const path = require('path');

const ALLOWED = ['name', 'description', 'license', 'compatibility', 'metadata', 'allowed-tools'];

function check(dir) {
  const problems = [];
  const file = path.join(dir, 'SKILL.md');
  if (!fs.existsSync(file)) return ['no SKILL.md in ' + dir];
  const text = fs.readFileSync(file, 'utf8');
  const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
  if (!fm) return ['frontmatter must start on line 1 with ---'];
  const keys = fm[1].split(/\r?\n/).filter((l) => /^[A-Za-z_-]+:/.test(l)).map((l) => l.split(':')[0]);
  for (const k of keys) if (!ALLOWED.includes(k)) problems.push('field not allowed on upload: ' + k);
  const get = (k) => { const m = new RegExp('^' + k + ':[ \\t]*(.*)$', 'm').exec(fm[1]); return m ? m[1].trim() : ''; };
  const name = get('name');
  const desc = get('description');
  if (!name) problems.push('name is required');
  if (!desc) problems.push('description is required');
  if (name && !/^[a-z0-9-]+$/.test(name)) problems.push('name must be lowercase letters, digits, hyphens');
  if (name.length > 64) problems.push('name is ' + name.length + ' chars, max 64');
  if (name && name !== path.basename(dir)) problems.push('name "' + name + '" does not match folder "' + path.basename(dir) + '"');
  if (desc.length > 200) problems.push('description is ' + desc.length + ' chars, claude.ai max is 200');
  if (/^[ \t]*!`/m.test(text)) problems.push('body uses !`command` injection, which does not run in Cowork');
  return problems;
}

let failed = 0;
for (const dir of process.argv.slice(2)) {
  const problems = check(dir);
  if (problems.length) { failed++; console.log('FAIL ' + dir); problems.forEach((p) => console.log('  - ' + p)); }
  else console.log('ok   ' + dir);
}
process.exit(failed ? 1 : 0);

Run it over a whole skills directory:

node check-skill.js ~/.claude/skills/*

The output on our pack before the trim, abbreviated:

ok   skills/auditing-dependencies
FAIL skills/fixing-flaky-tests
  - description is 206 chars, claude.ai max is 200
ok   skills/generating-readmes
FAIL skills/refactoring-safely
  - description is 220 chars, claude.ai max is 200

It exits non-zero on any failure, so it drops into a CI step or a pre-commit hook unchanged. It does not validate against the full spec. For that, the claude.com how-to points at the skills-ref validate tool in the agentskills repository, which is the authoritative check.

The other direction: pull account skills into local Claude Code

Once a skill is in your claude.ai account, Cowork and cloud sessions load it with no setup. Local Claude Code sessions do not, unless you ask.

The mechanism is a one-time non-interactive run with an environment variable set:

CLAUDE_CODE_SYNC_SKILLS=1 claude -p "List the skills you have available"

Claude Code downloads every skill enabled for your account into ~/.claude/skills/synced/, answers the prompt, and exits. The files stay on disk, so every later interactive session with the same claude.ai sign-in loads them. Run /skills and they appear under a claude.ai sync heading. Downloads only happen during a run with the variable set, so after you enable or change a skill on claude.ai, run the command again. Turning a skill off in your account removes it from the synced folder at the next sync; deleting the folder by hand just gets it downloaded again.

Three rules apply to synced skills that do not apply to skills you wrote locally, and they are worth memorizing because they explain most "my synced skill is not running" reports:

  • Any name collision loses. A synced skill whose name matches a built-in command, a bundled skill, a local skill at any level, a plugin skill, a .claude/commands/ file, or an MCP prompt is skipped, and the other one runs. The comparison ignores case and spacing, so a synced Commit cannot sit beside a local commit.
  • Local beats synced. A deploy skill in your project's .claude/skills/ overrides a synced deploy.
  • Bodies are inert in local sessions. Outside Cowork and the cloud, Claude Code does not run ! commands in a synced skill, does not attach @ file references, and leaves ${CLAUDE_PROJECT_DIR} as literal text. Frontmatter is honored everywhere, so an allowed-tools grant still flows through the normal permission prompt.

The folder name synced is reserved in all three local skill locations. A skill you author under that name is skipped.

Plugins: the route that carries more than skills

A plugin is the alternative to uploading skills one by one, and it is the right choice when your skills come with an MCP connector, a subagent, or a hook. Cowork installs plugins from Customize > Plugins, from Anthropic's marketplace, from a Git repository you add by URL (GitHub, including GitHub Enterprise, plus public GitLab and Bitbucket), or from a file you upload.

The plugin format is shared with Claude Code. The claude.com plugins overview says so directly, and points at the Claude Code plugins reference for the manifest. So a plugin you built for Claude Code, following our plugin tutorial, is structurally a Cowork plugin already. Two differences in behavior matter:

  • Not every component runs everywhere. Per the Help Center, the skills bundled in a plugin work in chat on the web, the Chat tab in Claude Desktop, and Cowork. Hooks and subagents run only in Cowork and appear greyed out in chat.
  • Plugin skills are namespaced as plugin-name:skill-name, so they never collide with a personal or synced skill of the same name. That alone is a reason to prefer plugins for a team-wide set.

The documented default limits for Cowork plugins: 200 MB uncompressed per package, 5,000 files per package, 500 plugins per marketplace, and 25 marketplaces per account. The in-app skill viewer previews files up to 1 MB; larger files are still available to Claude at runtime.

Anthropic's own knowledge-work marketplace is open source on GitHub, and its marketplace.json currently lists 98 plugins: 11 built by Anthropic (productivity, enterprise search, sales, finance, data, legal, marketing, customer support, product management, biology research, and Plugin Create) plus partner-built entries. If you are writing a skill for a business function, read the Anthropic plugin for that function first, so you write the thing it does not do.

Team sharing, recording, and what stays private

On Pro and Max, an uploaded skill is private to your account. That is the whole story.

On Team and Enterprise, skill sharing works in both chat and Cowork. From Customize > Skills, open a skill you created, click Share, and choose specific people, a group, or the entire organization. Shared skills are view-only for recipients, appear greyed out in their list until they toggle them on, and update automatically when you change the original. Owners can also provision skills organization-wide, which puts them in everyone's list with a team indicator. The Share button only appears once an owner has enabled at least one of the sharing toggles under Organization settings > Skills, and on Enterprise, skill content scanning can be turned on so third-party skills are checked for malicious content before they run.

One Cowork-native way to create a skill has no Claude Code equivalent. On Pro, Max, and Team plans, in Cowork on Claude for Mac, you can record a skill: Claude captures your screen, clicks, typing, and narration for up to about ten minutes, then proposes a SKILL.md for you to save, update, or dismiss. It is not available in chat, on Windows, or on Free and Enterprise plans. The video and audio are not retained; a set of screenshots is kept inside the Cowork task, so deleting the task deletes them.

FAQ

Why does Cowork not see my ~/.claude/skills folder? Because Cowork sessions run on Anthropic's servers and load skills from your claude.ai account, not from your machine. The Cowork overview says it "doesn't read the Claude Code CLI's ~/.claude directory." Upload the skill under Customize > Skills or ship it in a plugin.

Is a Cowork skill the same file format as a Claude Code skill? Yes, both are SKILL.md files following the Agent Skills specification. The difference is enforcement: an upload accepts only name, description, license, compatibility, metadata, and allowed-tools, caps the description at 200 characters, and requires the name to match the folder. Claude Code accepts a longer frontmatter table and only recommends description.

My skill uploaded but Claude never uses it in Cowork. What now? Check that it is toggled on under Customize > Skills, then rewrite the description. It is the only text Claude reads to decide whether to load the skill, so state when to use it in the first sentence with the phrases people actually type. If the skill relied on ! shell injection to pull in live data, that line is now a placeholder in Cowork, and the skill may have nothing to work with.

Can I use my claude.ai skills in local Claude Code? Yes. Run CLAUDE_CODE_SYNC_SKILLS=1 claude -p "..." once; the enabled skills download into ~/.claude/skills/synced/ and load in every later local session. A synced skill is skipped whenever its name matches any other command, and its body does not run shell commands or attach files in a local session.

Do I need a plugin, or is a skill enough? A skill is enough when the whole thing is instructions plus optional scripts and reference files. Reach for a plugin when you need to bundle an MCP connector, a subagent, or hooks, or when you want namespacing so a team-wide skill cannot collide with anyone's personal one.

Does the Cowork upload count against anything? The documented limits are on plugins, not individual skills: 200 MB uncompressed and 5,000 files per plugin package. For a plain skill, keep SKILL.md under 500 lines, which is the guidance on both the Claude Code and claude.com pages, and move reference material into separate files that load only when needed.

Which plans have Cowork and skills? Cowork needs a paid plan: Pro, Max, Team, or Enterprise, with web and mobile on Pro, Max, and Team, and on Enterprise where an admin has enabled it. Skills need code execution enabled. The Help Center lists skills as available on Free as well, while the claude.com overview lists paid plans only; if you are on Free, expect skills in chat at most, since Cowork itself is paid.