ReactiveX/rxjs · error · Error

${message}

Error message

${message}

What it means

Thrown by skill-install's assertContained when a skill installation target path resolves equal to the project root or outside it. It is the same path-traversal guard as the CLI version, applied to skill targets like .claude/skills/... to guarantee installs stay within the project.

Source

Thrown at packages/migrate/src/skill-install.ts:355

): SkillInstallResult {
  return {
    action,
    adapter: installation.adapter,
    compatibleHarnesses: installation.compatibleHarnesses,
    targetPath: installation.targetPath,
    provenancePath: installation.provenancePath,
    stateBefore,
    stateAfter,
    changed,
    provenance: installation.provenance,
    canonicalIntegrity: installation.provenance,
  };
}

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 canonicalFuturePath(path: string): Promise<string> {
  const missing: string[] = [];
  let existing = path;
  let canonicalExisting: string | undefined;
  while (canonicalExisting === undefined) {
    try {
      canonicalExisting = await realpath(existing);
    } catch (error: unknown) {
      if (!isMissingPathError(error)) throw error;
      const parent = dirname(existing);
      if (parent === existing) throw error;
      missing.push(relative(parent, existing));
      existing = parent;
    }
  }
  return resolve(canonicalExisting, ...missing.reverse());

View on GitHub (pinned to 54796b38a5)

Solutions

  1. Use the built-in harness adapters (claude/codex/cursor) rather than custom ones with relative escape paths
  2. Remove symlinks under the skill target path that point outside the project
  3. Set the adapter targetDirectory to a normal in-project relative path like '.claude/skills/rxjs-migrate'

Example fix

// before
const adapter = { adapterId: 'x', targetDirectory: '../outside', compatibleHarnesses: ['x'] };
// after
const adapter = { adapterId: 'x', targetDirectory: '.x/skills/rxjs-migrate', compatibleHarnesses: ['x'] };
Defensive patterns

Strategy: validation

Validate before calling

import { relative, isAbsolute } from 'node:path';
const r = relative(projectRoot, targetPath);
if (r === '' || r === '..' || r.startsWith(`..${sep}`) || isAbsolute(r)) throw new TypeError('skill target escapes project root');

Type guard

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

Prevention

When it happens

Trigger: An adapter whose localPath is '' or contains '..' so targetPath equals or escapes projectRoot; a canonicalFuturePath result that symlinks outside canonicalProjectRoot; custom adapters with bad targetDirectory values.

Common situations: Custom/buggy harness adapters; symlinks in the target path chain leading outside the repo; unusual adapter configuration.

Related errors


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