nextlevelbuilder/ui-ux-pro-max-skill · error · Error
Refusing to modify path outside repository: ${resolvedPath}
Error message
Refusing to modify path outside repository: ${resolvedPath} What it means
sync-assets.mjs guards every write with assertInsideRepo(): each target path is resolved to an absolute path and must start with repoRoot (the repo checkout containing cli/scripts). Because syncDir() deletes the target directory before copying, this guard prevents an accidental absolute/relative escape from rm-ing arbitrary directories. It fires when a computed target resolves outside the repo — usually a wrong CWD or a relocated script.
Source
Thrown at cli/scripts/sync-assets.mjs:64
/\.(ttf|otf|woff2?|png|jpe?g|gif|ico|coverage|pyc)$/i.test(rel);
// ponytail: all synced assets are text (csv/json/md/py); normalize CRLF->LF so
// the byte hash and the on-disk copy don't drift with git autocrlf across platforms.
const toLF = (text) => text.replace(/\r\n/g, '\n');
async function exists(path) {
try {
await access(path);
return true;
} catch {
return false;
}
}
function assertInsideRepo(path) {
const resolvedPath = resolve(path);
if (!resolvedPath.startsWith(repoRoot)) {
throw new Error(`Refusing to modify path outside repository: ${resolvedPath}`);
}
return resolvedPath;
}
async function listFiles(root) {
const files = [];
async function walk(dir) {
for (const entry of await readdir(dir, { withFileTypes: true })) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
await walk(fullPath);
} else if (entry.isFile()) {
files.push(relative(root, fullPath).replaceAll('\\', '/'));
}
}
}
View on GitHub (pinned to a38d04c3d5)
Solutions
- Run the script from its canonical location in the repo: `cd <repo>/cli && npm run sync:assets`.
- Ensure the full repository (src/ui-ux-pro-max, cli/, .claude/) is checked out — the script assumes the standard layout.
- If you intentionally relocated the script, update the repoRoot/assetRoot constants at the top of sync-assets.mjs to match the new layout.
- Do not pass absolute external targets to syncDir(); the guard will (correctly) refuse.
Defensive patterns
Strategy: validation
Validate before calling
// preflight: confirm the expected repo layout before syncing
import { access } from 'node:fs/promises';
const layoutOk = await Promise.all([
access('src/ui-ux-pro-max'),
access('cli/scripts/sync-assets.mjs'),
access('.claude/skills'),
]).then(() => true, () => false);
if (!layoutOk) throw new Error('Run sync-assets.mjs from a full checkout of the repo'); Prevention
- Always run `npm run sync:assets` from the repo's cli/ directory in a full clone.
- Don't relocate cli/ independently of src/ and .claude/.
- Treat this guard as a safety feature protecting rm -rf; fix the layout, never bypass the guard.
When it happens
Trigger: Running sync-assets.mjs after moving cli/ out of the repository (so __dirname/../.. no longer lands in the repo root while a target path does); invoking the script through a symlink that resolves elsewhere; someone editing the repoRoot/assetRoot constants to point outside the tree.
Common situations: Copying the cli folder into another project to reuse the script; running via a symlinked path; CI checking out into nested paths that break the '..','..' assumption.
Related errors
- Source directory does not exist: ${sourceDir}
- verified-at must use YYYY-MM-DD
- verified-at has suspicious date {value!r}
- catalog-summary.json is missing
AI-assisted analysis of nextlevelbuilder/ui-ux-pro-max-skill@a38d04c3d5 (2026-08-14).
Data as JSON: /api/errors/9c62c0d95979268b.
Report an issue: GitHub.