v0.2.76 — Skill upload hardening
v0.2.76 — Skill upload hardening
Closes data-safety and UX gaps in the skills upload flow surfaced by multi-agent review. Adds cross-tenant storage isolation, per-slug exclusion locking, blob lifecycle cleanup, and protects UI-uploaded skills from being clobbered by tale deploy. No schema-breaking changes; no manual migration required.
🔒 Security
- Bind each
_storageblob to the uploading org via a newskillUploadIntentstable;uploadSkillBundlenow refuses anystorageIdnot owned by the caller, closing a cross-tenant read/delete path on client-supplied storage ids (#1741) - Serialize concurrent
force: trueuploads to the same(orgId, slug)via a newskillUploadClaimsexclusion lock so racing uploads can no longer last-writer-wins past the existence check (#1741) - Wire all
_storageblob disposal through a singlecleanupUploadResourceshelper so cancel, parse failure, write failure, and success paths all delete both the blob and its intent row — no orphaned blobs per replace cycle (#1741) - Replace six empty
catch {}blocks across skill file IO witherrnoCode-gated logging (CLAUDE.md hard rule) so non-ENOENTfilesystem errors no longer disappear silently (#1741) scaffoldNewOrganization'scopyTreenow skips@<slug>/tenant-namespaced entries so creating a new org no longer recursively copies every other org's skills/integrations/workflows into the new namespace (#1741)
🛠 Improvements
tale deploypreserves UI-uploaded skills: any slug already present in the container is stripped from the host sync via the newlistContainerSkills+stageSkillsWithoutConflictingBundlespath (mirrors theprovidersSOPS-secrets branch); replaces the prior best-effort backup-to-.tale-backup/workaround (#1741)duplicateSkillnow preserves binary assets (PNG / PDF / fonts) by routing through a new buffer-safereadFileBufferSafe+atomicWriteBufferpair instead of the prior UTF-8 round-trip that corrupted non-text files; emits a dedicatedduplicate_skillaudit action (#1741)- macOS
__MACOSX/,.DS_Store, andThumbs.dbentries are filtered on both client and server parsers, so Finder's right-click → Compress output no longer fails with a misleading "missing SKILL.md" error (#1741) - Skills list updates without manual refresh after upload: dialog routes through
useUploadSkillBundle(was rawuseAction), andlib/config-watcher.tslearns theskillstypeMap so SSE-driven invalidation covers them (#1741) - Upload dialog cancels its in-flight blob POST and skips stale-state
setStateon close viaAbortController+isMountedRef; ConvexError toasts now show the human message viaextractErrorMessageinstead of a serialized error object (#1741) - Drop UX hardened: multi-file / folder / wrong-type drops produce specific errors; window-level
dragover/droppreventDefaultstops a missed drop from navigating the browser away (#1741) - Preview step now renders full SKILL.md frontmatter (license, recommended Python/Node, disable-model-invocation, unknown-key count) with an ICU plural file count; "Replace bundle" button added next to Duplicate / Delete in the detail panel (#1741)
- Upload audit row now carries
skillMdHash,totalBytes,license, anddisableModelInvocationso a rollback investigation can tie an audit entry to a specific bundle (#1741) - DE strings fixed per
TERMINOLOGY_DE.md(Wird hochgeladen…→Lade hoch…,Bundle wird gelesen…→Lese Bundle…);Zipaligned with.zip-Datei; orphanskills.asset.loadNotFoundkey renamed toskills.viewer.notFound(#1741)
🐛 Fixes
audit_mutations.ALLOWED_ACTIONSnow includesduplicate_skill, so the new audit action isn't rejected at write time (#1741)
📝 Other
- New
migrations/backfill_skill_scaffoldinginternal action re-runs the idempotent per-org scaffolder so organizations created before theskillsdomain existed get theirskills/directory seeded on demand (#1741) requireOrgMembershipById/requireOrgAdminOrDeveloperwidened to acceptActionCtx | MutationCtxso the new V8 upload mutations reuse the same auth + role gate (no behavior change for existing callers) (#1741)
Upgrade
No manual migration required. Run tale upgrade to update the CLI, then tale deploy to apply the new version.
Optional: if your deployment has organizations created before the skills scaffolding existed and you want their skills/ directory seeded without waiting for the first upload, run:
bunx convex run migrations/backfill_skill_scaffolding:backfillSkillScaffoldingThis is idempotent and only seeds missing domains; it does not touch any existing skill bundles.
Contributors
Full Changelog: v0.2.75...v0.2.76