ReactiveX/rxjs · error · Error

${message}

Error message

${message}

What it means

Thrown by assertContained when a candidate path resolves to the root itself or escapes outside the given root directory (the relative path from root to candidate is '' or starts with '..' or is absolute). The migrate CLI uses it as a path-traversal guard so outputs and skill installs stay inside the project root. This is a safety invariant, not a filesystem failure.

Source

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

  }
  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>'}`);
  }
  const outputPath = resolve(outputRoot, outputName);
  if (outputPath === outputRoot) throw new Error(`Output name must identify a file below outputRoot: ${outputName}`);
  assertContained(outputRoot, outputPath, `Output path is outside outputRoot: ${outputName}`);
  return outputPath;
}

function assertContained(root: string, candidate: string, message: string): void {
  const localPath = relative(root, candidate);
  if (localPath === '' || (localPath !== '..' && !localPath.startsWith(`..${sep}`) && !isAbsolute(localPath))) return;
  throw new Error(message);
}

async function canonicalDirectory(path: string, label: string): Promise<string> {
  const canonicalPath = await realpath(path);
  const pathStats = await stat(canonicalPath);
  if (!pathStats.isDirectory()) throw new Error(`${label} is not a directory: ${path}`);
  return canonicalPath;
}

async function canonicalFutureDirectory(path: string, label: string): Promise<string> {
  const resolved = await canonicalFuturePath(path);
  try {
    const pathStats = await stat(path);
    if (!pathStats.isDirectory()) throw new Error(`${label} is not a directory: ${path}`);
  } catch (error: unknown) {
    if (!isMissingPathError(error)) throw error;
  }
  return resolved;

View on GitHub (pinned to 54796b38a5)

Solutions

  1. Change the output/skill target path so it resolves strictly inside the configured root directory
  2. Remove '..' segments and absolute paths from output configuration
  3. If a symlink intentionally points outside the root, replace it with a real directory or move the target inside the root
  4. Pass the intended project root explicitly instead of relying on cwd resolution

Example fix

// before
await safeOutputPath(projectRoot, '/var/tmp/out');
// after
await safeOutputPath(projectRoot, 'var-tmp-out'); // stays under projectRoot
Defensive patterns

Strategy: validation

Validate before calling

import { relative, isAbsolute } from 'node:path';
const contained = (root: string, c: string) => {
  const r = relative(root, c);
  return r !== '' && (r === '..' || r.startsWith(`..${sep}`) || isAbsolute(r));
};
if (contained(root, candidate)) throw new TypeError(`Output escapes root: ${candidate}`);

Type guard

const isContainedPath = (root: string, candidate: string): boolean => {
  const r = relative(root, candidate);
  return r === '' || (r !== '..' && !r.startsWith(`..${sep}`) && !isAbsolute(r));
};

Try / catch

try { await safeOutputPath(root, out); } catch (e) { if (e instanceof Error && /outside|escape/i.test(e.message)) { /* reconfigure output path */ } throw e; }

Prevention

When it happens

Trigger: Calling planMigrationFiles, preflightPlanOutputs, or safeOutputPath with an output path like '../outside-dir' or '/etc' that resolves outside the resolved source/output root; also symlinked targets whose canonical path escapes the root.

Common situations: Passing an absolute output directory, using '..' segments in --output, or a symlink inside the project pointing elsewhere so the canonical path leaves the root.

Related errors


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