dmtrKovalenko / dmtrKovalenko/fff

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

Open
#830 1 comment 0 reactions 0 assignees View on GitHub
triaged
Dominant language
Rust
Stars
10.7k
Forks
446
Avg merge
1d 21h
Merged PRs (30d)
39

Description

### 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):

```ts
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//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/...`).

Contributor guide

No contributing guide indexed for this repository

Research direction

Start at the grep tool's execute entry point in src/index.ts, then read routePathConstraint in aux-finders.ts and reproduce the file-path cases from the issue. Confirm that an unindexed file path cannot silently trigger an unconstrained fuzzy search; done means the result is safely constrained or clearly reports that the path was not indexed and the constraint was dropped.

Written by the indexing model from the issue text.

Assessment

Tech stack
nodejs, typescript
Domain
search
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.