shadcn-ui/ui · error · Error

Invalid target path "${target}". Target paths using @${alias

Error message

Invalid target path "${target}". Target paths using @${aliasKey}/ must stay within the ${aliasKey} alias root.

What it means

Thrown by resolveAliasTarget when a file target of the form @aliasKey/subpath resolves to a path outside the alias root. After matching ^@([^/]+)/(.+)$ and confirming aliasKey is a known target alias, it computes aliasRoot = config.resolvedPaths[aliasKey] and resolvedPath = resolve(aliasRoot, targetPath); if resolvedPath is neither the aliasRoot itself nor beneath it, the target escapes and is rejected.

Source

Thrown at packages/shadcn/src/utils/updaters/update-files.ts:475

    return null
  }

  const [, aliasKey, targetPath] = match

  if (!isTargetAliasKey(aliasKey)) {
    return {
      target: `${aliasKey}/${targetPath}`,
    }
  }

  const aliasRoot = path.resolve(config.resolvedPaths[aliasKey])
  const resolvedPath = path.resolve(aliasRoot, targetPath)

  if (
    resolvedPath !== aliasRoot &&
    !resolvedPath.startsWith(`${aliasRoot}${path.sep}`)
  ) {
    throw new Error(
      `Invalid target path "${target}". Target paths using @${aliasKey}/ must stay within the ${aliasKey} alias root.`
    )
  }

  return {
    resolvedPath,
  }
}

function resolveFileTargetDirectory(
  file: z.infer<typeof registryItemFileSchema>,
  config: Config
) {
  if (file.type === "registry:ui") {
    return config.resolvedPaths.ui
  }

  if (file.type === "registry:lib") {

View on GitHub (pinned to efac598707)

Solutions

  1. Edit the offending target to remove ".." segments so it stays inside the alias root.
  2. Use a target that the alias root legitimately contains, e.g. "@components/ui/button.tsx".
  3. If you need a file in a different alias, switch the target to that alias's prefix.
  4. Re-run the add command after the registry item is fixed.

Example fix

// registry item (before)
{ "path": "x.ts", "target": "@components/../lib/x.ts", "type": "registry:ui" }
// after
{ "path": "x.ts", "target": "@lib/x.ts", "type": "registry:ui" }
Defensive patterns

Strategy: validation

Validate before calling

import path from "path"
function assertAliasTargetInside(target: string, aliasRoot: string) {
  const resolved = path.resolve(aliasRoot, target.replace(/^@[^/]+\//, ""))
  if (resolved !== aliasRoot && !resolved.startsWith(aliasRoot + path.sep)) {
    throw new Error(`target escapes alias root: ${target}`)
  }
}

Type guard

const isAliasTargetInside = (target: string, aliasRoot: string) => {
  const m = target.match(/^@([^/]+)\/(.+)$/)
  if (!m) return true
  const resolved = path.resolve(aliasRoot, m[2])
  return resolved === aliasRoot || resolved.startsWith(aliasRoot + path.sep)
}

Try / catch

try {
  await updateFiles(tree, config, options)
} catch (e) {
  if (e instanceof Error && /must stay within the .* alias root/.test(e.message)) {
    // rewrite the target to remove ".." and retry
  }
  throw e
}

Prevention

When it happens

Trigger: A registry file uses target like "@components/../lib/secret" so that path.resolve pushes it above the components alias root. The containment check `resolvedPath !== aliasRoot && !resolvedPath.startsWith(aliasRoot + sep)` fails and throws.

Common situations: Hand-written target with "../" that climbs out of the alias directory; refactoring an alias root without updating targets; malicious registry trying to write outside the alias.

Related errors


AI-assisted analysis of shadcn-ui/ui@efac598707 (2026-08-12). Data as JSON: /api/errors/6d85eac41b27627a. Report an issue: GitHub.