upstash/context7 · error · Error

Unsafe skill name: ${JSON.stringify(skillName)}

Error message

Unsafe skill name: ${JSON.stringify(skillName)}

What it means

Thrown by assertSkillNameInRoot() when isSafeSkillName() rejects the name. A safe name must be a string of length 1–128, not '.' or '..', contain no NUL byte, and match ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ (alphanumeric first char; only letters, digits, '.', '_', '-' afterwards). The error stringifies the name with JSON.stringify so control characters are visible.

Source

Thrown at packages/cli/src/utils/skill-name.ts:16

import { resolve, dirname, basename } from "path";

const SAFE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;

export function isSafeSkillName(name: string): boolean {
  if (typeof name !== "string") return false;
  if (name.length === 0 || name.length > 128) return false;
  if (name === "." || name === "..") return false;
  if (name.includes("\0")) return false;
  if (!SAFE_NAME.test(name)) return false;
  return true;
}

export function assertSkillNameInRoot(skillsRoot: string, skillName: string): string {
  if (!isSafeSkillName(skillName)) {
    throw new Error(`Unsafe skill name: ${JSON.stringify(skillName)}`);
  }
  const root = resolve(skillsRoot);
  const target = resolve(root, skillName);
  if (dirname(target) !== root || basename(target) !== skillName) {
    throw new Error(`Skill name "${skillName}" escapes the skills root`);
  }
  return target;
}

View on GitHub (pinned to ca15df0443)

Solutions

  1. Rename to start with an alphanumeric character and use only a-zA-Z0-9._- afterwards.
  2. Strip leading/trailing whitespace and any '/', '\\', '@', ':' from the name.
  3. Shorten to <= 128 characters.
  4. For scoped skills, flatten '@org/skill' to 'org.skill' or 'org-skill'.

Example fix

// before
assertSkillNameInRoot(root, "@upstash/context7"); // throws: contains '@' and '/'

// after
assertSkillNameInRoot(root, "upstash-context7");
Defensive patterns

Strategy: validation

Validate before calling

import { isSafeSkillName } from "../utils/skill-name";
function normalizeSkillName(raw: string): string {
  const trimmed = raw.trim().replace(/^[@]/, "").replace(/[\\/]+/g, "-");
  return trimmed;
}
const name = normalizeSkillName(input);
if (!isSafeSkillName(name)) {
  throw new Error(`"${input}" is not a valid skill name (use a-z0-9 and ._-, max 128 chars).`);
}
assertSkillNameInRoot(skillsRoot, name);

Type guard

// Direct reuse of the library predicate as a userland type guard.
import { isSafeSkillName } from "../utils/skill-name";
function asSkillName(raw: unknown): string | null {
  return typeof raw === "string" && isSafeSkillName(raw) ? raw : null;
}

Try / catch

try {
  assertSkillNameInRoot(skillsRoot, candidate);
} catch (e) {
  const msg = (e as Error).message;
  if (msg.startsWith("Unsafe skill name")) {
    throw new Error(`Refusing to install: ${msg}. Use only a-z0-9 and ._- characters.`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Empty string; name longer than 128 chars; name with '/', '\\', ':', space, or other punctuation; name starting with '.' or '-' or '_' (first char must be alphanumeric); name containing a NUL byte; non-string input (number/object).

Common situations: User typed a scoped registry id like '@org/skill' (contains '@' and '/'); copy-paste included a trailing slash or whitespace; name derived from a filesystem path with separators; very long auto-generated slug.

Related errors


AI-assisted analysis of upstash/context7@ca15df0443 (2026-08-12). Data as JSON: /api/errors/028ce783c8d58c72. Report an issue: GitHub.