ReactiveX/rxjs · error · Error

Output path is not a regular file: ${outputPath}

Error message

Output path is not a regular file: ${outputPath}

What it means

When a planned output path already exists and is neither a symlink nor a regular file (i.e. it is a directory, FIFO, socket, or device), assertWritableOutput refuses to proceed. Writing file bytes over such an entry is never safe — especially a directory — so the migrator aborts before touching anything.

Source

Thrown at packages/migrate/src/node.ts:159

  const canonicalOutputs = new Set<string>();
  for (const { sourcePath, outputPath, result } of plan.files) {
    if (result.status === 'refused') throw new Error(`Migration result was refused for source: ${sourcePath}`);
    const resolvedOutputPath = resolve(outputPath);
    if (resolvedOutputPath === outputRoot) throw new Error(`Output path must identify a file below outputRoot: ${outputPath}`);
    assertContained(outputRoot, resolvedOutputPath, `Output path is outside outputRoot: ${outputPath}`);
    const canonicalOutputPath = await canonicalFuturePath(resolvedOutputPath);
    assertContained(canonicalOutputRoot, canonicalOutputPath, `Output path resolves outside outputRoot: ${outputPath}`);
    if (canonicalOutputs.has(canonicalOutputPath)) throw new Error(`Duplicate output path: ${outputPath}`);
    canonicalOutputs.add(canonicalOutputPath);
    await assertWritableOutput(resolvedOutputPath, options.overwrite ?? false);
  }
}

async function assertWritableOutput(outputPath: string, overwrite: boolean): Promise<void> {
  try {
    const outputStats = await lstat(outputPath);
    if (outputStats.isSymbolicLink()) throw new Error(`Refusing to overwrite a symbolic link: ${outputPath}`);
    if (!outputStats.isFile()) throw new Error(`Output path is not a regular file: ${outputPath}`);
    if (!overwrite) throw new Error(`Output path already exists; enable overwrite explicitly: ${outputPath}`);
  } catch (error: unknown) {
    if (!isMissingPathError(error)) throw error;
  }
}

export async function migrateTestFiles(options: MigrateFilesOptions): Promise<readonly MigratedFile[]> {
  if (options.write && !options.outputRoot) {
    throw new Error('outputRoot is required when write is enabled.');
  }
  const plan = await planMigrationFiles(options);
  return options.write ? applyMigrationPlan(plan, { overwrite: options.overwrite }) : plan.files;
}

function safeOutputPath(outputRoot: string, outputName: string): string {
  if (!outputName || outputName === '.' || isAbsolute(outputName)) {
    throw new Error(`Output name must be a non-empty relative path: ${outputName || '<empty>'}`);
  }

View on GitHub (pinned to 54796b38a5)

Solutions

  1. Remove or rename the directory currently occupying the output path
  2. Fix outputName so it always produces a file-like name with an extension
  3. Choose a different outputRoot that does not contain conflicting directory names

Example fix

// before
outputName: ({ sourcePath }) => basename(sourcePath, '.spec.ts'), // collides with dir

// after
outputName: ({ sourcePath }) => basename(sourcePath, '.spec.ts') + '.spec.ts',
Defensive patterns

Strategy: validation

Validate before calling

import { lstat } from 'node:fs/promises';
async function allOutputsAreRegularFilesOrMissing(plan: { files: { outputPath: string }[] }): Promise<boolean> {
  for (const f of plan.files) {
    try { if (!(await lstat(f.outputPath)).isFile()) return false; }
    catch (e) { if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e; }
  }
  return true;
}

Prevention

When it happens

Trigger: Calling applyMigrationPlan when a planned files[].outputPath exists and lstat reports a non-file, non-symlink type; most commonly outputPath names an existing directory.

Common situations: An outputName function that drops the file extension, making outputPath collide with an existing directory of the same name; leftover directories from a previous tool run.

Related errors


AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28). Data as JSON: /api/errors/4a8871a0bfe361e7. Report an issue: GitHub.