ReactiveX/rxjs · error · Error
Canonical Skill root is not a directory: ${canonicalSkillRoo
Error message
Canonical Skill root is not a directory: ${canonicalSkillRoot} What it means
Thrown by resolveInstallation when options.canonicalSkillRoot resolves to an existing path that is not a directory. The canonical skill root is normally supplied internally by resolveCanonicalSkillRoot, so this usually indicates a broken package layout or a bad explicit override.
Source
Thrown at packages/migrate/src/skill-install.ts:184
return manageSkillInstallation(operation, installOptions);
}
function adapterFor(harness: SkillHarness): {
readonly adapter: SkillInstallAdapter;
readonly localPath: string;
readonly compatibleHarnesses: readonly SkillHarness[];
} {
const adapter = skillHarnessAdapters[harness];
return { adapter: adapter.adapterId, localPath: adapter.targetDirectory, compatibleHarnesses: adapter.compatibleHarnesses };
}
async function resolveInstallation(options: SkillInstallOptions): Promise<ResolvedInstallation> {
const projectRoot = resolve(options.projectRoot);
const canonicalProjectRoot = await realpath(projectRoot);
if (!(await stat(canonicalProjectRoot)).isDirectory()) throw new Error(`Project root is not a directory: ${projectRoot}`);
const canonicalSkillRoot = await realpath(resolve(options.canonicalSkillRoot));
if (!(await stat(canonicalSkillRoot)).isDirectory()) throw new Error(`Canonical Skill root is not a directory: ${canonicalSkillRoot}`);
const adapter = adapterFor(options.harness);
const targetPath = resolve(projectRoot, adapter.localPath);
assertContained(projectRoot, targetPath, 'Skill target must remain below the project root.');
const canonicalTargetPath = await canonicalFuturePath(targetPath);
assertContained(canonicalProjectRoot, canonicalTargetPath, 'Skill target resolves outside the project root.');
const integrity = await inspectSkillIntegrity(canonicalSkillRoot);
const provenance: SkillInstallProvenance = {
...integrity,
installSchemaVersion: skillInstallSchemaVersion,
adapter: adapter.adapter,
compatibleHarnesses: adapter.compatibleHarnesses,
targetPath: adapter.localPath.replaceAll(sep, '/'),
};
return {
projectRoot,
canonicalSkillRoot,
targetPath,View on GitHub (pinned to 54796b38a5)
Solutions
- Reinstall @rxjs/migrate to restore the skill directory
- If calling programmatically, pass the directory returned by resolveCanonicalSkillRoot
- Verify the path with stat before invoking
Example fix
// before
manageSkillInstallation('check', { harness: 'claude', canonicalSkillRoot: './SKILL.md', projectRoot: '.' });
// after
manageSkillInstallation('check', { harness: 'claude', canonicalSkillRoot: await resolveCanonicalSkillRoot(), projectRoot: '.' }); Defensive patterns
Strategy: validation
Validate before calling
import { stat } from 'node:fs/promises';
if (!(await stat(skillRoot)).isDirectory()) throw new TypeError(`canonicalSkillRoot not a directory: ${skillRoot}`); Type guard
const isDir = async (p: string) => (await stat(p)).isDirectory();
Prevention
- Always derive canonicalSkillRoot from resolveCanonicalSkillRoot
- Reinstall the package if assets look wrong
When it happens
Trigger: Passing a custom canonicalSkillRoot option pointing at a file; or the packaged skill/ directory being replaced by a file in a mangled install.
Common situations: Programmatic use of manageSkillInstallation with a wrong canonicalSkillRoot; corrupted @rxjs/migrate install.
Related errors
- Project root is not a directory: ${projectRoot}
- --out-dir is required with --write
- ${message}
- ${label} is not a directory: ${path}
- An output path ancestor is not a directory: ${existing}
AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28).
Data as JSON: /api/errors/d6c632b70d7f3468.
Report an issue: GitHub.