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
- Use a path relative to the workspace root, e.g. 'src/lib' instead of '/abs/path/src/lib'.
- Check the process cwd is the intended workspace root before querying.
- Strip or rebase the absolute path against cwd yourself and verify it does not start with '../' before passing it.
- 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
- Always pass workspace-relative paths.
- Rebase absolute paths against cwd before querying.
- Log cwd at startup to catch wrong-working-directory issues.
- Reject inputs starting with '../' at your API boundary.
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
- Failed to canonicalize path
- fff native library not found. Run `npx @ff-labs/fff-node…
- Unsupported platform
- Unsupported architecture
- No npm package available for platform
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)