yamadashy/repomix · error · PathScopeError

Path "${input}" must be relative to workspace root — no "/",

Error message

Path "${input}" must be relative to workspace root — no "/", "~/", "../", drive, or ".." segment. Use e.g. "src/index.ts" or "." for whole workspace.

What it means

MCP path scoping: resolveWithinRoot rejects any input path that lexically escapes the workspace root — absolute paths, drive/UNC paths, '~/...' home refs, or any '..' segment — throwing PathScopeError before any fs access. This is a security guard keeping MCP tool calls confined to the workspace.

Source

Thrown at src/mcp/pathScope.ts:65

 * Rejected: absolute paths ("/x", "C:\x", "\x", UNC), "~" home refs, and any
 * ".." traversal segment (either separator). Symlink escapes are caught via
 * realpath. Returns the resolved absolute path (realpath when the target exists).
 */
export const resolveWithinRoot = async (
  root: string,
  input: string,
  deps: { realpath: RealpathFn } = { realpath: (p) => fs.realpath(p) },
): Promise<string> => {
  const rootResolved = path.resolve(root);

  if (input === '' || input === '.') {
    return rootResolved;
  }

  // Reject anything that escapes the workspace root — absolute/drive/UNC, a "~"
  // home ref, or any ".." segment. A redundant leading "./" or ".\" is allowed.
  if (isEscapingPath(input)) {
    throw new PathScopeError(
      `Path "${input}" must be relative to workspace root — no "/", "~/", "../", drive, or ".." segment. Use e.g. "src/index.ts" or "." for whole workspace.`,
    );
  }

  const candidate = path.resolve(rootResolved, input);

  if (!isInside(rootResolved, candidate)) {
    throw new PathScopeError(`Path "${input}" resolves outside workspace root.`);
  }

  // Resolve symlinks to catch a link inside root that points back out. If a path
  // can't be resolved via realpath (e.g. the target does not exist yet, or the
  // root itself can't be stat'd), fall back to the lexical path — it is already
  // confined lexically above.
  let realRoot: string;
  try {
    realRoot = (await deps.realpath(rootResolved)) as string;
  } catch {

View on GitHub (pinned to f465ad9093)

Solutions

  1. Pass workspace-root-relative paths, e.g. 'src/index.ts' or '.' for the whole workspace
  2. Strip any leading home or drive prefix from the path before calling
  3. Resolve the path against the workspace root yourself and re-submit the relative form
  4. If the target genuinely lives outside the root, launch/restart the MCP server with that directory as the root

Example fix

// before
resolveWithinRoot(root, '/home/me/project/src/index.ts')
// after
resolveWithinRoot(root, 'src/index.ts')
Defensive patterns

Strategy: validation

Validate before calling

const isSafeRelative = (p: string) =>
  p !== '' && !p.startsWith('/') && !p.startsWith('~') && !/^[a-zA-Z]:/.test(p) &&
  !p.split(/[\\/]/).includes('..');
if (!isSafeRelative(input)) throw new Error('path must be relative to workspace root');

Try / catch

try {
  const abs = await resolveWithinRoot(root, input);
} catch (err) {
  if (err instanceof PathScopeError) {
    console.error(`Rejected path ${input}; send a workspace-relative path like src/index.ts.`);
  } else throw err;
}

Prevention

When it happens

Trigger: An MCP tool call passes an absolute path ('/etc/passwd'), a home-relative path ('~/notes.txt'), a Windows drive path ('C:\\x'), or a path containing '..' to resolveWithinRoot.

Common situations: LLM agents constructing absolute paths from previous tool output; clients on Windows sending drive-qualified paths; callers assuming the MCP server accepts OS-wide paths like the CLI does.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of yamadashy/repomix@f465ad9093 (2026-08-29). Data as JSON: /api/errors/97accc894f3bfec9. Report an issue: GitHub.