Skip to content

v0.2.76 — Skill upload hardening

Choose a tag to compare

@larryro larryro released this 26 May 07:54
71b3efa

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 _storage blob to the uploading org via a new skillUploadIntents table; uploadSkillBundle now refuses any storageId not owned by the caller, closing a cross-tenant read/delete path on client-supplied storage ids (#1741)
  • Serialize concurrent force: true uploads to the same (orgId, slug) via a new skillUploadClaims exclusion lock so racing uploads can no longer last-writer-wins past the existence check (#1741)
  • Wire all _storage blob disposal through a single cleanupUploadResources helper 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 with errnoCode-gated logging (CLAUDE.md hard rule) so non-ENOENT filesystem errors no longer disappear silently (#1741)
  • scaffoldNewOrganization's copyTree now 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 deploy preserves UI-uploaded skills: any slug already present in the container is stripped from the host sync via the new listContainerSkills + stageSkillsWithoutConflictingBundles path (mirrors the providers SOPS-secrets branch); replaces the prior best-effort backup-to-.tale-backup/ workaround (#1741)
  • duplicateSkill now preserves binary assets (PNG / PDF / fonts) by routing through a new buffer-safe readFileBufferSafe + atomicWriteBuffer pair instead of the prior UTF-8 round-trip that corrupted non-text files; emits a dedicated duplicate_skill audit action (#1741)
  • macOS __MACOSX/, .DS_Store, and Thumbs.db entries 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 raw useAction), and lib/config-watcher.ts learns the skills typeMap so SSE-driven invalidation covers them (#1741)
  • Upload dialog cancels its in-flight blob POST and skips stale-state setState on close via AbortController + isMountedRef; ConvexError toasts now show the human message via extractErrorMessage instead of a serialized error object (#1741)
  • Drop UX hardened: multi-file / folder / wrong-type drops produce specific errors; window-level dragover / drop preventDefault stops 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, and disableModelInvocation so 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…); Zip aligned with .zip-Datei; orphan skills.asset.loadNotFound key renamed to skills.viewer.notFound (#1741)

🐛 Fixes

  • audit_mutations.ALLOWED_ACTIONS now includes duplicate_skill, so the new audit action isn't rejected at write time (#1741)

📝 Other

  • New migrations/backfill_skill_scaffolding internal action re-runs the idempotent per-org scaffolder so organizations created before the skills domain existed get their skills/ directory seeded on demand (#1741)
  • requireOrgMembershipById / requireOrgAdminOrDeveloper widened to accept ActionCtx | MutationCtx so 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:backfillSkillScaffolding

This is idempotent and only seeds missing domains; it does not touch any existing skill bundles.

Contributors

@larryro


Full Changelog: v0.2.75...v0.2.76