Cowork plugin upload: SKILL.md frontmatter angle brackets silently fail validation

Resolved 💬 2 comments Opened May 21, 2026 by samuelrbarker Closed May 25, 2026

Summary

Cowork's .plugin bundle upload (Directory → Plugins → Personal → Add plugin) rejects bundles with Plugin validation failed whenever any SKILL.md YAML frontmatter contains literal angle brackets in a quoted string — even when:

  • The bundle passes claude plugin validate locally.
  • The YAML parses correctly with every standard parser.
  • The same bundle structure (sans the offending bracket pair) installs cleanly.

The rejection is silent in ~/Library/Logs/Claude/main.log (only the request shows, no response error) and in claude.ai-web.log (no MARKETPLACE_ERROR:* entry, unlike marketplace-sync failures which return per-plugin detail). The UI surfaces only Plugin validation failed with no further information.

Reproduction

  1. Create a minimal plugin with name: wp-tools (or any name), one skill, and this frontmatter:

``yaml
---
name: site-setup
description: >-
Triggers on \"add site\", \"configure site\", \"add <domain> to wp-tools\",
or any onboarding request.
---
``

  1. Zip into a .plugin bundle and upload via Cowork's \"Add plugin\" UI.
  2. Receive Plugin validation failed. No local log detail.

Remove the <domain> substring (or any other <word>-style placeholder in a quoted YAML value) and the same bundle installs cleanly.

Diagnosis

Binary-searched a real wp-tools bundle (5 skills) over ~8 incremental builds. Isolated the failure to a single SKILL.md frontmatter, then to the frontmatter (not the body), then to a single character pair (< and >).

The placeholder <domain> is a common pattern when describing trigger phrases for skills (matches the convention used throughout Anthropic's own skill examples for arguments like <filename>, <path>, etc.). Plugin authors writing SKILL.md descriptions are likely to hit this.

The probable cause is a server-side validator running the frontmatter through HTML-aware sanitization that treats <word> as an unclosed tag. The fact that the rejection is silent makes the bug particularly hard to discover.

What would help

  1. Surface the actual server-side rejection reason in either the UI or the local upload log. The marketplace-sync path already does this (per-plugin error array); the bundle-upload path does not.
  2. Don't HTML-sanitize YAML string values — they are not HTML.
  3. At minimum, document the restriction in plugins-reference under SKILL.md frontmatter conventions.

Environment

  • Claude Desktop (macOS), Cowork
  • Bundle built via the documented zip-the-plugin-root convention; same build script produces other plugins that upload fine.
  • Both private-repo and brand-new-name plugins exhibit the behavior; ruled out via incremental binary search.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗