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

  1. Run the script from its canonical location in the repo: `cd <repo>/cli && npm run sync:assets`.
  2. Ensure the full repository (src/ui-ux-pro-max, cli/, .claude/) is checked out — the script assumes the standard layout.
  3. If you intentionally relocated the script, update the repoRoot/assetRoot constants at the top of sync-assets.mjs to match the new layout.
  4. 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

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


AI-assisted analysis of nextlevelbuilder/ui-ux-pro-max-skill@a38d04c3d5 (2026-08-14). Data as JSON: /api/errors/9c62c0d95979268b. Report an issue: GitHub.