Skip to content

fix(search): accept file paths instead of crashing with ENOTDIR (#827)#832

Closed
Dave-London wants to merge 2 commits intomainfrom
fix/search-accept-file-path
Closed

fix(search): accept file paths instead of crashing with ENOTDIR (#827)#832
Dave-London wants to merge 2 commits intomainfrom
fix/search-accept-file-path

Conversation

@Dave-London
Copy link
Copy Markdown
Owner

Summary

  • Fixes pare-search__search and pare-search__count crashing with spawn ENOTDIR when path points to a file. The schema description already promises file or directory; now both work.
  • pare-search__find (fd) genuinely walks directories, so it now surfaces a typed path must be a directory for find error instead of leaking the raw Node ENOTDIR.
  • Missing paths now produce a clear path does not exist: <path> error.

Closes #827.

Root cause

packages/server-search/src/tools/search.ts (and count.ts, find.ts) used the user-supplied path as the spawn cwd unconditionally:

const cwd = path || process.cwd();

child_process.spawn throws ENOTDIR synchronously when cwd is set to a non-directory path, so the error leaked out as { "error": "spawn ENOTDIR" }.

Fix

New helper resolveSearchPath(path) in lib/search-runner.ts returns { cwd, target, isFile }:

  • undefinedcwd = process.cwd(), target = "."
  • directory → cwd = <dir>, target = "." (existing behaviour)
  • file → cwd = dirname(file), target = <absolute-file-path>, isFile = true
  • missing → throws path does not exist: <path>

search.ts and count.ts route through it and append target instead of a hard-coded ".". count.ts also adds --with-filename when isFile, because rg drops the file: prefix when given a single file and the parser expects file:count.

find.ts resolves the path the same way but throws when the input is a file (fd is not meaningful for a single file root).

Before / after

// Before
search({ path: "/repo/pnpm-lock.yaml", pattern: "^  hono@" })
// → { "error": "spawn ENOTDIR" }

// After
search({ path: "/repo/pnpm-lock.yaml", pattern: "^  hono@" })
// → { matches: [...], totalMatches: 2 }

Test plan

  • pnpm --filter @paretools/search build — clean
  • pnpm --filter @paretools/search test — 88/88 passing (added 5 unit tests for resolveSearchPath, plus 4 integration tests covering search/count file-path success and find/missing-path typed errors)
  • pnpm lint on touched files — clean
  • prettier --check on touched files — clean
  • CI matrix (ubuntu/windows/macos x node 20/22)

`mcp__pare-search__search` and `count` previously used the user-supplied
`path` as the spawn `cwd` unconditionally. When `path` pointed to a file
(which the schema description explicitly promises to support — "Directory
or file to search in"), Node threw `spawn ENOTDIR` because cwd must be a
directory.

This adds `resolveSearchPath()` in the runner, which:
  - returns process.cwd() + "." for an undefined path (existing behaviour)
  - returns the directory itself + "." for a directory path
  - returns the file's parent dir + the absolute file path for a file
  - throws a clear "path does not exist" error for missing paths

`search` and `count` now route through it. `count` additionally passes
`--with-filename` when the target is a single file so its parser keeps
the `file:count` shape.

`find` (fd) genuinely needs a directory root, so it surfaces a typed
`path must be a directory for find` error instead of leaking ENOTDIR.

Closes #827.
The new resolveSearchPath export in @paretools/search/src/lib/search-runner.ts
broke vi.mock() in both small-servers smoke suites — vitest fails the import
when a code path under test references a missing export on a mocked module.

Default the mock to the directory case ({cwd: path, target: ".", isFile: false})
so existing tests stay green; tests that need to exercise the file-input path
can override per-test.

Caught only by smoke job — package-level tests pass without this since they
don't mock search-runner.
@Dave-London
Copy link
Copy Markdown
Owner Author

Closing as redundant — the two commits in this PR (31af1fb, 508fd43) are already on main. Issue #827 is closed.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pare-search: spawn ENOTDIR when path is a file (schema says file or directory)

1 participant