JuliusBrussee/caveman · error

invalid scoped package

Error message

invalid scoped package

What it means

barePackageName() parses a bare import specifier and throws "invalid scoped package" when a specifier starting with "@" does not have a scope and name — i.e. "@" alone, "@scope", or "@scope/". The parser needs a well-formed @scope/name prefix to find the package's package.json; a malformed scoped specifier cannot be resolved.

Source

Thrown at packages/agent/src/source-graph.ts:211

async function collectPackageFiles(directory: string, files: Set<string>): Promise<void> {
  const entries = await opendir(directory);
  for await (const entry of entries) {
    if (entry.name === "node_modules" || entry.name === ".git") continue;
    const path = resolve(directory, entry.name);
    if (entry.isDirectory()) {
      await collectPackageFiles(path, files);
    } else if (entry.isFile()) {
      files.add(path);
    } else if (entry.isSymbolicLink()) {
      throw new Error(`caveman build: package artifact symlink is not lockable: ${JSON.stringify(path)}`);
    }
  }
}

function barePackageName(specifier: string): string {
  const parts = specifier.split("/");
  if (specifier.startsWith("@")) {
    if (parts.length < 2 || parts[1] === "") throw new Error("invalid scoped package");
    return `${parts[0]}/${parts[1]}`;
  }
  if (parts[0] === "") throw new Error("invalid package");
  return parts[0]!;
}

function resolvePackageExport(exportsValue: unknown, subpath: string): string | undefined {
  if (typeof exportsValue === "string" || Array.isArray(exportsValue)) {
    return subpath === "." ? resolveConditionalExport(exportsValue) : undefined;
  }
  if (!isRecord(exportsValue)) return undefined;
  const keys = Object.keys(exportsValue);
  if (!keys.some((key) => key.startsWith("."))) {
    return subpath === "." ? resolveConditionalExport(exportsValue) : undefined;
  }
  if (Object.hasOwn(exportsValue, subpath)) {
    return resolveConditionalExport(exportsValue[subpath]);
  }

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Fix the specifier to a complete form: "@scope/name" or "@scope/name/subpath".
  2. If the specifier is computed at build time, validate it before emitting the import.
  3. Search the codebase for the malformed literal from the error message.

Example fix

// before
import x from "@myorg/";

// after
import x from "@myorg/pkg";
Defensive patterns

Strategy: type-guard

Validate before calling

const SCOPED = /^@[^/\s]+\/.+$/;
function isWellFormedBareSpecifier(spec: string): boolean {
  if (spec.startsWith("@")) return SCOPED.test(spec) && !spec.endsWith("/");
  return /^[^/]/.test(spec);
}

Type guard

function isScopedPackageName(spec: string): boolean {
  const parts = spec.split("/");
  return spec.startsWith("@") && parts.length >= 2 && parts[1] !== "";
}

Prevention

When it happens

Trigger: A source file contains an import like import "@scope/" or a string built from a template that collapses to just "@scope" or "@".

Common situations: Template-built import strings with an empty submodule segment; typos like "@scope//sub"; refactors that split a specifier incorrectly.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/d74bc9eb130067d7. Report an issue: GitHub.