dmtrKovalenko/fff · error · Error

Path constraint must be relative to the workspace

Error message

Path constraint must be relative to the workspace: ${pathConstraint}

What it means

normalizePathConstraint validates user-supplied path constraints in query.ts. If the value is an absolute path that resolves outside the workspace (relative path escapes via ../) it is rejected. The library only accepts path constraints that stay inside the current working directory.

Solutions

  1. Use a path relative to the workspace root, e.g. 'src/lib' instead of '/abs/path/src/lib'.
  2. Check the process cwd is the intended workspace root before querying.
  3. Strip or rebase the absolute path against cwd yourself and verify it does not start with '../' before passing it.
  4. If you intended to search another project, change the workspace/cwd rather than passing an external path.

Example fix

// before
query({ pathConstraint: '/home/me/project/src/util.ts' })
// after
query({ pathConstraint: 'src/util.ts' })
Defensive patterns

Strategy: validation

Validate before calling

function isRelativeInsideWorkspace(p, cwd) {
  if (typeof p !== 'string' || !p.trim()) return false;
  if (!path.isAbsolute(p)) return true;
  const rel = path.relative(cwd, p).replaceAll(path.sep, '/');
  return rel !== '' && !rel.startsWith('../') && rel !== '..' && !path.isAbsolute(rel);
}

Type guard

const isSafePathConstraint = (p) => typeof p === 'string' && p.trim().length > 0 && !p.startsWith('/');

Try / catch

try {
  await query({ pathConstraint: raw });
} catch (e) {
  if (String(e.message).includes('Path constraint must be relative')) {
    return query({ pathConstraint: path.relative(cwd, raw) });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling a query/search API with pathConstraint set to an absolute path outside cwd (e.g. '/etc' or '/home/user/other-project'), or a path that normalizes to a parent of the workspace, or an absolute path equal to cwd's parent.

Common situations: Passing an absolute file path copied from an editor instead of a relative one; running the tool from a different cwd than expected so a previously-valid relative path now escapes; passing a sibling directory path like '/repo/../other' .

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 dmtrKovalenko/fff@7f8537e70f (2026-09-10). Data as JSON: /api/errors/6942e89a84ead204. Report an issue: GitHub.

Appendix: source

Thrown at packages/pi-fff/src/query.ts:14

import path from "node:path";

export function normalizePathConstraint(
  pathConstraint: string,
  cwd = process.cwd(),
): string | null {
  let trimmed = pathConstraint.trim();
  if (!trimmed) return trimmed;

  if (path.isAbsolute(trimmed)) {
    const relative = path.relative(cwd, trimmed).replaceAll(path.sep, "/");
    if (relative === "") return null;
    if (relative.startsWith("../") || relative === ".." || path.isAbsolute(relative)) {
      throw new Error(
        `Path constraint must be relative to the workspace: ${pathConstraint}`,
      );
    }
    trimmed = relative;
  }

  if (trimmed === "." || trimmed === "./") return null;
  // Strip a leading `./` so `./**/*.rs` and `**/*.rs` behave identically.
  if (trimmed.startsWith("./")) trimmed = trimmed.slice(2);

  // wif we left with the ** it means anything so treat it as a cwd path
  if (trimmed === "**" || trimmed === "**/" || trimmed === "**/*") return null;

  // FFF's glob matcher can treat a hidden directory root glob such as
  // `.agents/**` as empty, while the tool contract says this means "inside
  // this directory". Collapse simple trailing recursive directory globs to the
  // directory-prefix constraint understood by the parser. Keep real file globs
  // such as `src/**/*.ts` unchanged.

View on GitHub (pinned to 7f8537e70f)