claude-code - 💡(How to fix) Fix [BUG] Cowork plugin upload: server validation_errors[] swallowed — UI shows only generic "Plugin validation failed." [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#56376Fetched 2026-05-06 06:29:45
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
0
Timeline (top)
labeled ×4commented ×1cross-referenced ×1

Error Message

That regex captures the top-level error.message from the server (which is just "Plugin validation failed." itself), but the actually useful info is in error.details.validation_errors[]. None of it surfaces in the UI, in logs/main.log, or anywhere else on disk. "type": "error", "error": { I only got to the actual error by replaying the upload from the renderer's DevTools Console using fetch with credentials: "include". That's not a workflow most users will find. Cowork should surface error.details.validation_errors[] to the user. Even a minimal change like joining the messages would be sufficient: const details = parsedBody?.error?.details?.validation_errors;

Error Messages/Logs

Related: #56303 (same generic error, different cause), #56246 (same generic error for plugin name prefix).

Root Cause

  1. Build a .plugin with a SKILL.md whose description frontmatter is over 1024 characters (or any other server-validation violation).
  2. Upload via the Cowork plugin install dialog.
  3. Observe the generic "Plugin validation failed." toast.
  4. Verify logs/main.log contains no detail and DevTools Network tab is empty (because the upload runs in the Electron main process).

Code Example

if (o.message.includes("plugin_validation") || o.message.includes("HTTP 400")) {
  const a = o.message.match(/\): (.+)$/);
  const c = a?.[1] ?? "Plugin validation failed.";
  throw new SN(c, true);
}

---

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "Plugin validation failed.",
    "details": {
      "validation_errors": [
        {
          "error_code": "plugin_upload_skill_md_description_too_long",
          "message": "Skill 'skills/auto-capture': field 'description' in SKILL.md must be at most 1024 characters"
        }
      ],
      "error_visibility": "user_facing",
      "error_code": "plugin_upload_validation_failed"
    }
  }
}

---

const details = parsedBody?.error?.details?.validation_errors;
const message = details?.length
  ? details.map(v => v.message).join('\n')
  : (regexMatch?.[1] ?? "Plugin validation failed.");
throw new SN(message, true);

---

14:15:48 [info] [remoteUploadOps] uploadPluginViaRemote filename=vault-skills.zip bytes=13044 overwrite=false
14:15:49 [info] [remoteMarketplaceClient] uploadAccountPlugin marketplaceId=marketplace_xxx filename=vault-skills.zip bytes=13044 overwrite=false
(no further entries about this upload)
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report
  • I am using the latest version of Claude Code

What's Wrong?

When a .plugin upload is rejected by the server-side validator, Cowork shows the toast "Plugin validation failed." with no further detail — even though the server returns a structured response body that names the failing file, the specific rule it broke, and the limit it exceeded.

The detail is dropped in c2n / uploadPluginViaRemote (in app.asar.vite/build/index.js). The catch block does:

if (o.message.includes("plugin_validation") || o.message.includes("HTTP 400")) {
  const a = o.message.match(/\): (.+)$/);
  const c = a?.[1] ?? "Plugin validation failed.";
  throw new SN(c, true);
}

That regex captures the top-level error.message from the server (which is just "Plugin validation failed." itself), but the actually useful info is in error.details.validation_errors[]. None of it surfaces in the UI, in logs/main.log, or anywhere else on disk.

Concrete example

Server response when a SKILL.md description is too long:

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "Plugin validation failed.",
    "details": {
      "validation_errors": [
        {
          "error_code": "plugin_upload_skill_md_description_too_long",
          "message": "Skill 'skills/auto-capture': field 'description' in SKILL.md must be at most 1024 characters"
        }
      ],
      "error_visibility": "user_facing",
      "error_code": "plugin_upload_validation_failed"
    }
  }
}

What the user sees: "Plugin validation failed." No file name, no rule, no limit. The user has no path forward without inspecting the network response, which isn't possible from the renderer (the upload happens in the main process via net.fetch, so DevTools Network tab is empty).

I only got to the actual error by replaying the upload from the renderer's DevTools Console using fetch with credentials: "include". That's not a workflow most users will find.

Related undocumented limit

The 1024-char description cap on SKILL.md frontmatter isn't in the public plugin reference (https://code.claude.com/docs/en/plugins-reference). Worth either documenting, or at minimum surfacing the violation message — but the underlying UX bug is generic regardless of which specific validation rule fires.

What Should Happen?

Cowork should surface error.details.validation_errors[] to the user. Even a minimal change like joining the messages would be sufficient:

const details = parsedBody?.error?.details?.validation_errors;
const message = details?.length
  ? details.map(v => v.message).join('\n')
  : (regexMatch?.[1] ?? "Plugin validation failed.");
throw new SN(message, true);

Bonus: log the full server response body in main.log when the upload fails, so users without DevTools fluency have a fallback.

Error Messages/Logs

logs/main.log shows the upload starting but nothing about the failure:

14:15:48 [info] [remoteUploadOps] uploadPluginViaRemote filename=vault-skills.zip bytes=13044 overwrite=false
14:15:49 [info] [remoteMarketplaceClient] uploadAccountPlugin marketplaceId=marketplace_xxx filename=vault-skills.zip bytes=13044 overwrite=false
(no further entries about this upload)

Steps to Reproduce

  1. Build a .plugin with a SKILL.md whose description frontmatter is over 1024 characters (or any other server-validation violation).
  2. Upload via the Cowork plugin install dialog.
  3. Observe the generic "Plugin validation failed." toast.
  4. Verify logs/main.log contains no detail and DevTools Network tab is empty (because the upload runs in the Electron main process).

Claude Model

n/a — UI bug, not model-related.

Is this a regression?

Unsure. Has likely been this way since the account-uploads marketplace shipped.

Claude Code Version

Claude 1.5354.0 (Cowork desktop app)

Platform

Anthropic API

Operating System

Windows 11 (MSIX install)

Additional Information

Related: #56303 (same generic error, different cause), #56246 (same generic error for plugin name prefix).

extent analysis

TL;DR

The issue can be fixed by modifying the error handling in uploadPluginViaRemote to surface the detailed validation errors from the server response.

Guidance

  • Modify the catch block in uploadPluginViaRemote to parse the error.details.validation_errors array from the server response and include the detailed error messages in the thrown error.
  • Consider logging the full server response body in main.log when the upload fails to provide a fallback for users without DevTools access.
  • Update the error handling to join the validation error messages with newline characters to display a list of errors to the user.
  • Review the public plugin reference documentation to ensure that all validation rules and limits are properly documented.

Example

const details = parsedBody?.error?.details?.validation_errors;
const message = details?.length
  ? details.map(v => v.message).join('\n')
  : (regexMatch?.[1] ?? "Plugin validation failed.");
throw new SN(message, true);

Notes

The proposed fix assumes that the server response format is consistent and that the error.details.validation_errors array is always present when validation errors occur. Additional error handling may be necessary to account for variations in the server response.

Recommendation

Apply the workaround by modifying the error handling in uploadPluginViaRemote to surface the detailed validation errors, as this will provide a better user experience and help with debugging.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] Cowork plugin upload: server validation_errors[] swallowed — UI shows only generic "Plugin validation failed." [1 comments, 2 participants]