Summary
toolcraft-openapi@0.0.71 rejects generated skill output paths when any component is a symlink, even if the symlink resolves to a path inside the project directory.
This blocks poe-agent-tools from running npm start because that repo has:
.claude/skills -> ../.agents/skills
The symlink is intentionally internal to the repo, but generation fails with:
Error: Generated skill output must remain inside the project directory.
at assertSafeProjectPath (.../node_modules/toolcraft-openapi/dist/bin/generate.js:562:23)
at async restoreGeneratedSkillFiles (.../node_modules/toolcraft-openapi/dist/bin/generate.js:624:9)
at async syncGeneratedClient (.../node_modules/toolcraft-openapi/dist/bin/generate.js:159:13)
Repro
From poe-agent-tools:
The script does:
npm install --force toolcraft@latest toolcraft-openapi@latest
npm run generate
Generation then writes generated skill docs to the hard-coded path:
.claude/skills/poe-agent-tools/SKILL.md
But .claude/skills is a symlink to ../.agents/skills, so the safety check rejects it.
Current behavior
assertSafeProjectPath rejects if any path component is a symbolic link.
That prevents unsafe symlink escapes, but it also rejects safe project-internal links like:
<repo>/.claude/skills -> <repo>/.agents/skills
The failed run can also leave partial generated output behind because rollback hits the same symlink check.
Expected behavior
Generated skill output should be allowed when the resolved/canonical target remains inside the project directory.
A symlink should only be rejected if resolving it escapes the project root, e.g. into another repo, the user home directory, /tmp, etc.
Conceptually:
const root = await fs.realpath(cwd);
const targetParent = await fs.realpath(path.dirname(filePath));
const relative = path.relative(root, targetParent);
if (
relative === ".." ||
relative.startsWith(`..${path.sep}`) ||
path.isAbsolute(relative)
) {
throw new Error("Generated skill output must remain inside the project directory.");
}
This would preserve the protection against path escapes while allowing repo-internal symlink layouts.
Additional context
toolcraft-openapi-generate --inspect --output-format markdown reports the upstream OpenAPI document is fully compatible:
100% compatible · 35 operations · 35 supported · 0 unsupported
So the issue is not OpenAPI compatibility. It is the generated skill-file path safety check interacting with a repo-internal symlink.
A related improvement would be to make the generated skill output path configurable instead of hard-coding .claude/skills/<name>/SKILL.md, but relaxing the symlink check for canonical in-project targets is the direct fix.
Summary
toolcraft-openapi@0.0.71rejects generated skill output paths when any component is a symlink, even if the symlink resolves to a path inside the project directory.This blocks
poe-agent-toolsfrom runningnpm startbecause that repo has:The symlink is intentionally internal to the repo, but generation fails with:
Repro
From
poe-agent-tools:The script does:
Generation then writes generated skill docs to the hard-coded path:
But
.claude/skillsis a symlink to../.agents/skills, so the safety check rejects it.Current behavior
assertSafeProjectPathrejects if any path component is a symbolic link.That prevents unsafe symlink escapes, but it also rejects safe project-internal links like:
The failed run can also leave partial generated output behind because rollback hits the same symlink check.
Expected behavior
Generated skill output should be allowed when the resolved/canonical target remains inside the project directory.
A symlink should only be rejected if resolving it escapes the project root, e.g. into another repo, the user home directory,
/tmp, etc.Conceptually:
This would preserve the protection against path escapes while allowing repo-internal symlink layouts.
Additional context
toolcraft-openapi-generate --inspect --output-format markdownreports the upstream OpenAPI document is fully compatible:So the issue is not OpenAPI compatibility. It is the generated skill-file path safety check interacting with a repo-internal symlink.
A related improvement would be to make the generated skill output path configurable instead of hard-coding
.claude/skills/<name>/SKILL.md, but relaxing the symlink check for canonical in-project targets is the direct fix.