jackwener/OpenCLI · error · ManifestImportError

failed to scan ${filePath}: ${getErrorMessage(cause)}

Error message

failed to scan ${filePath}: ${getErrorMessage(cause)}

What it means

loadManifestEntries reads a CLI adapter module from clis/ to extract command definitions for the build manifest. If the file cannot be read from disk, it throws a ManifestImportError naming the file and the underlying cause, aborting that scan with a clear message instead of a raw fs error.

Source

Thrown at src/build-manifest.ts:156

 * Throws `ManifestImportError` when a file looks like a CLI module but its
 * import or post-import processing fails — callers must decide whether to
 * surface or aggregate the failure.
 *
 * The third argument `clisDir` is used to compute the POSIX-style
 * `sourceFile` relative path; it defaults to the package's `clis/` dir so
 * existing test callers stay backward-compatible.
 */
export async function loadManifestEntries(
  filePath: string,
  site: string,
  importer: (moduleHref: string) => Promise<unknown> = moduleHref => import(moduleHref),
  clisDir: string = CLIS_DIR,
): Promise<ManifestEntry[]> {
  let src: string;
  try {
    src = fs.readFileSync(filePath, 'utf-8');
  } catch (err) {
    throw new ManifestImportError(filePath, err);
  }

  // Helper / test modules that do not call cli() are not commands.
  if (!CLI_MODULE_PATTERN.test(src)) return [];

  try {
    const modulePath = toModulePath(filePath, site);
    const registry = getRegistry();
    const before = new Map(registry.entries());
    const mod = await importer(pathToFileURL(filePath).href);

    const exportedCommands = Object.values(isRecord(mod) ? mod : {})
      .filter(value => isCliCommandValue(value, site));

    const runtimeCommands = exportedCommands.length > 0
      ? exportedCommands
      : [...registry.entries()]
        .filter(([key, cmd]) => {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Confirm the file at filePath exists and is readable (ls -l, check permissions).
  2. Run the manifest build from the repository root so the default CLIS_DIR resolves correctly.
  3. Restore the deleted/renamed module or update the scanner's directory configuration.
  4. If on Linux/macOS, check filename casing matches the import/discovery path exactly.

Example fix

// before
buildManifest({ clisDir: path.join(process.cwd(), 'Cli') });
// after
buildManifest({ clisDir: path.join(process.cwd(), 'clis') });
Defensive patterns

Strategy: try-catch

Validate before calling

import { accessSync, constants } from 'fs';
function canRead(p) { try { accessSync(p, constants.R_OK); return true; } catch { return false; } }

Type guard

function isManifestImportError(e: unknown): e is ManifestImportError {
  return e instanceof Error && 'filePath' in e;
}

Try / catch

try {
  const entries = await loadManifestEntries(filePath);
} catch (err) {
  if (err instanceof ManifestImportError) {
    console.error(`skipping unreadable module ${err.filePath}:`, err.cause);
    return [];
  }
  throw err;
}

Prevention

When it happens

Trigger: fs.readFileSync fails on the module path — file deleted between discovery and read, wrong clisDir passed, case-mismatched filename on case-sensitive filesystems, or permission denied.

Common situations: Renaming/deleting a clis/*.ts file while a build runs; running the manifest build from a different working directory so the relative clis dir doesn't resolve; symlinked repos where CLIS_DIR points elsewhere.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/91912c51cb41cd38. Report an issue: GitHub.