mastra-ai/mastra · warning · HTTPException

Could not derive skill ID from registry skill metadata.

Error message

Could not derive skill ID from registry skill metadata.

What it means

After importing files, the handler derives a skill ID by slugifying the snapshot name (falling back to the asserted safe skill name) and throws a 400 HTTPException when the derived ID is empty — i.e. neither the registry snapshot's name nor the fetched skillId yields a non-empty slug. This prevents persisting a skill with an unusable identifier.

Source

Thrown at packages/server/src/server/handlers/builder-registry.ts:366

        });
      }

      const safeSkillId = assertSafeSkillName(result.skillId);
      const files = buildFileTree(result.files);

      // Parse SKILL.md frontmatter into structured fields. Splitting
      // frontmatter (name/description) from the markdown body keeps the
      // body as the agent-facing `instructions` instead of polluting it
      // with raw YAML metadata. SKILL.md missing or unparseable simply
      // yields a null snapshot — registry-provided values then fill in.
      const snapshot = parseSkillSnapshot(result.files);

      const resolvedName = snapshot?.name ?? safeSkillId;
      const description = snapshot?.description ?? `Imported from ${owner}/${repo}`;
      const id = toSlug(resolvedName) || safeSkillId;

      if (!id) {
        throw new HTTPException(400, {
          message: 'Could not derive skill ID from registry skill metadata.',
        });
      }

      // Reject collisions instead of silently overwriting; UI offers "Open existing".
      const existing = await skillStore.getById(id);
      if (existing) {
        throw new HTTPException(409, {
          message: `Skill with id "${id}" already exists.`,
          // Surface the existing id so the client can deep-link.
          cause: { storedSkillId: id },
        });
      }

      // Match the standard create flow: no caller = always public, otherwise default private.
      const authorId = getCallerAuthorId(requestContext) ?? undefined;
      const visibility: 'private' | 'public' = authorId ? (bodyVisibility ?? 'private') : 'public';

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Ensure the skill in the registry has a valid name in its metadata (SKILL.md frontmatter or registry manifest).
  2. Check the upstream skill's directory/metadata naming — rename it to a normal identifier and republish.
  3. Verify registry metadata format matches what this server version expects (upgrade server/registry pairing together).
  4. Inspect fetchSkillFiles output to confirm skillId/snapshot.name are populated.

Example fix

// before
---
name: "!!!"
description: ""
---

// after
---
name: "code-review"
description: "Review code for issues"
---
Defensive patterns

Strategy: validation

Validate before calling

function deriveSkillId(snapshotName?: string, fetchedId?: string): string {
  return slugify(snapshotName ?? '') || (fetchedId ?? '') || '';
}
if (!deriveSkillId(meta.name, meta.skillId)) throw new Error('registry metadata has no usable name — fix upstream skill');

Type guard

function hasUsableName(meta: { name?: string; skillId?: string }): boolean {
  return slugify(meta.name ?? '') !== '' || !!meta.skillId;
}

Prevention

When it happens

Trigger: Installing a registry skill whose metadata lacks a usable name (empty/missing snapshot.name) AND whose skillId cannot pass assertSafeSkillName or slugify to a non-empty value.

Common situations: Malformed upstream registry metadata; a skill directory name composed entirely of characters stripped by toSlug (e.g. all punctuation/emoji); a registry response shape change after a server upgrade.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/742c62539e1c69c5. Report an issue: GitHub.