[BUG] Cowork plugin upload fails with generic "Plugin validation failed" when a `description` field in any SKILL.md frontmatter contains angle brackets (`<…>`)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
When uploading a plugin to Cowork via Organization settings → Plugins, the upload is rejected with a generic "Plugin validation failed" and no further detail in the UI.
After bisecting over ~10 builds by stripping frontmatter fields one at a time, the trigger turned out to be the literal characters < and > appearing inside the description: field of any skills/<skill>/SKILL.md frontmatter. The validator appears to be HTML/XML-sanitizing that field and treating <x> as a tag — but the rejection is silent (no specific error pointing at the offending file or character), so it took many iterations to find.
For us, the offending text was a perfectly natural placeholder convention in a CLI-style description: Pass \<name> <prompt>\ as arguments. The exact same SKILL.md is accepted by Claude Code CLI without issue — only the Cowork upload validator rejects it.
What Should Happen?
Two things, in order of importance:
- Surface a specific, actionable error message. Instead of
"Plugin validation failed", the upload UI should say something like"description in skills/deputy/SKILL.md contains disallowed characters: '<', '>'", pointing at the file and field. The generic error makes this prohibitively expensive to diagnose — there's no way for an admin to tell whether the rejection is about file size, file extension, ZIP structure, manifest schema, an individual SKILL.md, or any specific character.
- Ideally, allow
<…>indescription. It's valid in a YAML scalar and natural for documenting CLI argument placeholders (<name>,<prompt>,<path>, etc.). If the validator must strip/escape to be HTML-safe in the marketplace UI, it should escape on render, not reject on input.
Error Messages/Logs
UI shows only:
Plugin validation failed
No further detail in the upload modal, no expandable error, no console output, no network response body visible to the admin user. Nothing in the org's audit log either, as far as we could tell.
Steps to Reproduce
- Create a minimal plugin directory with this structure:
````
my-plugin/
├── .claude-plugin/
│ └── plugin.json
└── skills/
└── example/
└── SKILL.md
.claude-plugin/plugin.json:
``json``
{
"name": "my-plugin",
"version": "0.1.0",
"description": "Repro for Cowork upload validation"
}
skills/example/SKILL.md— note the angle brackets in the description:
``markdown<name> <prompt>` as arguments to dispatch an example.
---
name: example
description: Pass
---
# Example skill
Body text.
```
- Zip the plugin:
cd my-plugin && zip -r ../my-plugin.zip .
- Upload
my-plugin.zipvia Cowork Organization settings → Plugins → Upload.
Result: Upload rejected with "Plugin validation failed".
- Now edit
skills/example/SKILL.mdand replace<name> <prompt>with[name] [prompt](or any non-<>form). Rebuild the zip and re-upload.
Result: Upload succeeds with no other changes.
Claude Model
None
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.153
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
Bisection ruled out the following as causes (all of these pass validation):
- Square brackets in
description([name] [prompt]) - Backticks in
description(`name prompt`) - Em-dash
—and other non-ASCII characters indescription - Long descriptions (~500 chars)
- The
argument-hint:frontmatter field - The
allowed-tools:frontmatter field - The contents of bash scripts under the skill's directory
- The presence of
.pytest_cache/directories inside the zip - File extension
.pluginvs.zip - Nested vs flat zip layout (
plugin/prefix vs no prefix)
Only < / > in the description field triggers the rejection.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗