Skip to content

ffgrep: path constraint targeting an unindexed file silently widens to a repo-wide fuzzy search #830

Description

@bfzha

Summary

ffgrep with a path targeting a file outside the index (e.g. anything under node_modules/, which is gitignore/exclusion-respected) returns zero exact matches, then silently re-runs the search repo-wide with the path constraint dropped, presenting unrelated files as if they were related to the pinned path.

Environment

  • OS: Windows 11
  • pi: 0.84.3
  • @ff-labs/pi-fff: 0.10.5 (tools-and-ui mode)
  • CWD: Node project with node_modules/ (react installed); node_modules/react/package.json exists on disk and actually contains jsx-runtime in its exports.

Reproduction

All commands run from the repo root:

  1. ffgrep { pattern: "jsx-runtime", path: "node_modules/react/package.json" }
    -> [0 exact matches. Maybe you meant this?] then matches from package-lock.json and tests/ — never from the pinned file (or anything under node_modules).

  2. ffgrep { pattern: "version", path: "node_modules/react/package.json" }
    -> first result is e2e/file-link-repro.spec.ts:50: version: 3, — completely unrelated to react.

  3. ffgrep { pattern: "sideEffects", path: "node_modules/react/package.json" }
    -> returns src/renderer/** files whose top hit is a useEffect line — fuzzy noise with zero relationship to either the query or the pinned path. (Because results are frecency-ranked, the App.tsx family shows up first for generic words.)

Control cases for contrast:

  • path: "node_modules/react/" (directory constraint, no trailing extension) + jsx-runtime -> clean No matches found (no leak — directory constraints keep the constrained query).
  • path: "this-dir-does-not-exist-xyz/" -> clean No matches found.
  • path: "package-lock.json" (an indexed file) -> works correctly.

Root cause

In src/index.ts (the grep tool's execute):

  • Workspace-relative paths never reach an aux finder: routePathConstraint (aux-finders.ts) returns null for paths without a ../ prefix, so the query runs against the workspace finder, whose index excludes node_modules. Exact grep => 0 items.
  • On 0 items, pathTargetsFile is computed from the trailing extension, and when true the fuzzy fallback uses fuzzyQuery = pattern without the path constraint (the "file may just be misnamed" heuristic):
const lastSeg = params.path?.split(/[\/]/).pop() ?? "";
const pathTargetsFile = /\.[a-zA-Z][a-zA-Z0-9]{0,9}$/.test(lastSeg);
const fuzzyQuery = pathTargetsFile ? pattern : query;
const fuzzy = picker.grep(fuzzyQuery, { mode: "fuzzy", ... });

The heuristic never checks whether the pinned file is actually reachable in the picker/index. For any unindexed file path — and node_modules/... is always unindexed — every grep is guaranteed to hit this fallback and return repo-wide noise.

Why it's dangerous

  • An agent pinning path: "node_modules/<pkg>/package.json" to check a dependency's contents receives matches from arbitrary source files, plausibly presented as if they concerned that file.
  • Short, common patterns (version, sideEffects, ...) amplify the problem: the fuzzy results are barely related to the pattern at all.

Expected behavior

  1. Don't broaden when the target isn't in the index. If pathTargetsFile and the pinned path is not part of the picker's index (gitignored/excluded), return a clear message such as path not indexed (gitignored or excluded): no matches — or, better, read/match that single file on disk so the query actually answers the question.
  2. At minimum, state that the constraint was dropped. When the fallback does broaden repo-wide, the output must say: path constraint ignored (target not indexed); results are repo-wide.
  3. The fuzzy fallback for common/generic patterns degrades into pure noise (sideEffects -> useEffect files). Consider suppressing or clearly labeling fuzzy results.

Notes

  • Directory constraints (no trailing extension) keep the constrained query and are safe — verified No matches found, no leak.
  • Aux finders currently can't rescue this case because routePathConstraint refuses workspace-relative paths even when they point outside the indexed set (e.g. node_modules/react/...).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions