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
- Use the built-in harness adapters (claude/codex/cursor) rather than custom ones with relative escape paths
- Remove symlinks under the skill target path that point outside the project
- 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
- Use built-in harness adapters
- Keep skill targetDirectory as a plain in-project relative path
- Avoid symlinks inside the skill target chain
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
- ${message}
- ${label} is not a directory: ${path}
- An output path ancestor is not a directory: ${existing}
- Project root is not a directory: ${projectRoot}
- Canonical Skill root is not a directory: ${canonicalSkillRoo
AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28).
Data as JSON: /api/errors/18935028178262f9.
Report an issue: GitHub.