[BUG] Plugin account-upload is last-write-wins with no compare-and-swap, so concurrent sessions silently destroy each other's hook fixes

Status Open
Maintainer reply None cached
Activity 0 comments · opened Sep 18, 2026

Cowork on claude.ai, account-level plugin uploads.

POST /api/organizations/{org}/marketplaces/{mkt}/plugins/account-upload?overwrite=true replaces the whole plugin. There is no If-Match, no version precondition, and no rejection when the caller's base is stale. Two sessions working in parallel therefore overwrite each other's work with a 200 and no warning anywhere.

Observed tonight on one account, three times in ninety minutes, on the same plugin:

  • session A uploads 0.9.7 with two hook fixes; a peer session uploads 0.9.15 from an older base; A's fixes are gone.
  • A re-applies onto 0.9.15 and uploads 0.9.16, verified by downloading the plugin back; a peer uploads again; the fixes are gone a second time and a new gate file has appeared that A never had.
  • The only way A discovered either loss was downloading the plugin and grepping its contents. The API's updated_at moves on every upload, so it cannot distinguish "my upload landed" from "someone else's landed after mine".

The practical effect is that a user-visible defect stays live while every session believes it fixed it, which is much worse than a failed write.

What would fix it

  • A precondition on the upload: If-Match on the plugin's current version/etag, 409 on mismatch, so a stale base is refused rather than silently applied.
  • Failing that, return the resulting content hash in the upload response so a caller can tell its own write apart from a later one.

Skills on this account have exactly this solved by going through a git repo with a real merge; plugins have no equivalent, and the upload API is the only route (there is no JSON write route — PUT/PATCH on the plugin answer 404/405, Allow: DELETE).

Environment

Cowork on claude.ai, Chrome on macOS, Opus 5. Related: #95465, #95408.

View original on GitHub ↗