[BUG] Account Skills have no read-back and no compare-and-swap, so concurrent sessions silently destroy each other's edits
Summary
Updating an account Skill is a whole-file replace with no way to read the stored body back and no concurrency control. Two sessions editing the same skill on the same day will silently overwrite one another, with no error on either side and no way to notice except by luck.
This happened three times in two days on my account (25–26 Aug 2026), on two different skills, to sessions that were each individually doing everything correctly.
Steps to reproduce
- Session A copies the synced skill body, adds a section, uploads it (via the claude.ai skills endpoints —
rename-skillaside,upload-skill,delete-skillthe aside copy, since there is no update verb). - Session A verifies: the skill is listed,
updated_atis fresh, the synced mirror matches byte for byte. Correct at that moment. - Two hours later, Session B — which took its base before A's upload — adds its own section and uploads.
- A's section is gone. No error is surfaced to either session. Nothing on the account records that a version ever existed with A's text in it.
What makes it undetectable rather than merely racy
- No read endpoint.
GET skills/{id},skills/get-skill,skills/download-skill,skills/{id}/filesall 404. The account holds no readable copy of a skill's body, so a session cannot diff its intended write against what is actually stored — not before writing, and not after. - No compare-and-swap.
upload-skilltakes nosha,if-match,base_versionor equivalent. There is no way to say "write this only if the stored copy is still the one I edited". - No version history. Once overwritten, the previous body is unrecoverable from the platform.
- The local synced mirror is not a substitute. It lags by minutes (sometimes much longer), and on some surfaces it is a session-start snapshot that never refreshes, so a stale read there is indistinguishable from a lost write.
updated_atmoving proves a write landed, not whose. Both sessions see a fresh timestamp after their own upload.
Why this matters more for skills than for ordinary files
Skills are the mechanism by which an agent's own operating rules persist. A silently lost skill edit is a corrected behaviour that quietly reverts — the failure resurfaces later as the agent repeating a mistake it had already been taught not to make, with nothing in the record explaining why.
What would fix it, in order of preference
- A read endpoint returning the stored
SKILL.md(and ideally the bundledreferences/,scripts/). This alone makes the problem detectable, which is most of the value. - Compare-and-swap on upload: accept an opaque version token from
list-skillsand reject the write with409if the stored version moved. GitHub'sshaparameter on contents writes is the exact shape. - Version history, so an overwrite is recoverable rather than terminal.
- A true update verb. Today the only route is rename-aside → upload → delete, which widens the window and strands a
-prevcopy whenever a session dies mid-sequence.
Current workaround, for anyone hitting this
An external mutex (a lock file in a private repo, created without a sha so creation is an atomic test-and-set), plus re-reading the synced mirror immediately before packaging, plus a content check after upload — and when the check fails, rebasing onto the other session's version rather than re-uploading, since re-uploading turns one lost edit into two. It narrows the window to seconds; it cannot close it, because the platform exposes nothing to close it with.
Related
- #87071 — no in-session way to save/update a skill, and no confirmation that a save happened. Neighbouring but distinct: that one is about the absence of a save path; this one is about the save path that does exist being unsafe under concurrency and unverifiable afterwards.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗