fix(core): bypass globby in stdin mode to avoid hang on macOS#931
fix(core): bypass globby in stdin mode to avoid hang on macOS#931
Conversation
Fixes #903 When using --stdin with explicit file paths (e.g., git ls-files | repomix --stdin), the application could hang on macOS when .gitignore files were present in the explicit file list. This happened because globby tried to use .gitignore as both a source of ignore rules AND a match target, creating a pathological state. Solution: - In stdin mode, completely bypass globby for explicit files - Run globby only for includePatterns (if configured) - Manually read and parse .gitignore from root directory - Filter explicit files using minimatch with loaded ignore patterns - Merge globby results and filtered explicit files - Remove duplicates This ensures: - .gitignore rules are respected in stdin mode - No hang occurs even with .gitignore in the file list - includePatterns work correctly alongside stdin files Limitations: - Currently only reads root .gitignore (subdirectory .gitignore files not supported in stdin mode) - This is acceptable since git ls-files already respects all .gitignore files Changes: - Modified src/core/file/fileSearch.ts to implement bypass logic - Updated existing tests to match new behavior - Added new tests for: - .gitignore rules applied to stdin files - Merging globby and explicit file results - Empty directory handling in stdin mode - All 41 tests pass - Manual testing confirms no hang and correct filtering Co-Authored-By: yamadashy <koukun0120@gmail.com>
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughThe changes implement stdin-mode file discovery by introducing explicit file handling that bypasses globby, manually applies ignore rules, and integrates additional ignore sources (.gitignore, .repomixignore). Normal mode behavior is preserved, and verbose tracing is added for debugging. Changes
Sequence Diagram(s)sequenceDiagram
participant Caller
participant FileSearch as fileSearch()
participant Globby
participant IgnoreFiles as .gitignore<br/>.repomixignore
participant Minimatch as minimatch<br/>(filter)
Caller->>FileSearch: explicitFiles provided (stdin mode)
rect rgb(200, 220, 255)
Note over FileSearch,IgnoreFiles: Load ignore patterns
FileSearch->>IgnoreFiles: Read .gitignore
FileSearch->>IgnoreFiles: Read .repomixignore
FileSearch->>FileSearch: Aggregate ignore patterns
end
rect rgb(220, 200, 255)
Note over FileSearch,Globby: Process files
FileSearch->>Globby: Run globby (include patterns only)
Globby-->>FileSearch: globby results
FileSearch->>Minimatch: Filter explicit files<br/>against aggregated ignore patterns
Minimatch-->>FileSearch: filtered explicit files
end
rect rgb(200, 255, 220)
Note over FileSearch: Merge & deduplicate
FileSearch->>FileSearch: Merge globby + filtered explicit files
FileSearch->>FileSearch: Deduplicate filePaths
end
FileSearch-->>Caller: Final filePaths
rect rgb(255, 240, 200)
Note over FileSearch: Normal mode (no explicitFiles)
FileSearch->>Globby: Use traditional globby flow<br/>with include/exclude patterns
Globby-->>FileSearch: Results
FileSearch-->>Caller: filePaths
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Key areas requiring attention:
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
[pre_merge_check_pass] The PR successfully addresses the core objective from issue #903 by fixing the macOS hang when using stdin with explicit file paths. The root cause is identified (globby treating .gitignore as both an ignore rule source and match target), a practical solution is implemented (bypassing globby for explicit stdin files), and the fix is demonstrated to work through unit tests (all 41 passing), manual testing, and performance verification on macOS. The solution preserves stdin support, file filtering, and ignore pattern functionality as required. While the PR identifies a different root cause than initially suspected (globby pathology rather than worker/pool interaction), it provides an effective fix with thorough testing and verification steps, satisfying the primary objectives of issue #903. | ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @yamadashy, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a critical bug causing the application to hang on macOS when processing files via standard input ( Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
Deploying repomix with
|
| Latest commit: |
636a75b
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://b57e69ea.repomix.pages.dev |
| Branch Preview URL: | https://fix-stdin-hang-bypass-globby.repomix.pages.dev |
There was a problem hiding this comment.
Code Review
This pull request effectively resolves a critical hanging issue on macOS when using stdin mode by bypassing globby for explicit file paths. The new logic correctly handles ignore patterns manually and merges the results, and the changes are well-supported by updated and new tests. My feedback focuses on improving code maintainability by addressing a couple of instances of code duplication that were introduced with the new logic. By refactoring these parts into helper functions, the code will be cleaner and easier to manage in the future.
| if (config.ignore.useGitignore) { | ||
| // Read .gitignore from root directory | ||
| const gitignorePath = path.join(rootDir, '.gitignore'); | ||
| try { | ||
| const gitignoreContent = await fs.readFile(gitignorePath, 'utf8'); | ||
| const gitignorePatterns = parseIgnoreContent(gitignoreContent); | ||
| allIgnorePatterns.push(...gitignorePatterns); | ||
| logger.trace('Loaded .gitignore patterns:', gitignorePatterns); | ||
| } catch (error) { | ||
| // .gitignore might not exist, which is fine | ||
| logger.trace( | ||
| 'No .gitignore found or could not read:', | ||
| error instanceof Error ? error.message : String(error), | ||
| ); | ||
| } | ||
|
|
||
| // If no include patterns at all, default to all files | ||
| if (includePatterns.length === 0) { | ||
| includePatterns = ['**/*']; | ||
| } | ||
| // Read .repomixignore from root directory | ||
| const repomixignorePath = path.join(rootDir, '.repomixignore'); | ||
| try { | ||
| const repomixignoreContent = await fs.readFile(repomixignorePath, 'utf8'); | ||
| const repomixignorePatterns = parseIgnoreContent(repomixignoreContent); | ||
| allIgnorePatterns.push(...repomixignorePatterns); | ||
| logger.trace('Loaded .repomixignore patterns:', repomixignorePatterns); | ||
| } catch (error) { | ||
| // .repomixignore might not exist, which is fine | ||
| logger.trace( | ||
| 'No .repomixignore found or could not read:', | ||
| error instanceof Error ? error.message : String(error), |
There was a problem hiding this comment.
The logic for reading and parsing .gitignore and .repomixignore is duplicated. To improve maintainability and reduce code repetition, you can extract this logic into a helper function.
if (config.ignore.useGitignore) {
const readIgnoreFile = async (fileName: string) => {
try {
const content = await fs.readFile(path.join(rootDir, fileName), 'utf8');
const patterns = parseIgnoreContent(content);
allIgnorePatterns.push(...patterns);
logger.trace(`Loaded ${fileName} patterns:`, patterns);
} catch (error) {
// Ignore file might not exist, which is fine
logger.trace(
`No ${fileName} found or could not read:`,
error instanceof Error ? error.message : String(error),
);
}
};
await readIgnoreFile('.gitignore');
await readIgnoreFile('.repomixignore');
}|
|
||
| logger.trace('Include patterns:', patterns); | ||
|
|
||
| filePaths = await globby(patterns, { | ||
| cwd: rootDir, | ||
| ignore: [...adjustedIgnorePatterns], | ||
| ignoreFiles: [...ignoreFilePatterns], | ||
| onlyFiles: true, | ||
| absolute: false, | ||
| dot: true, | ||
| followSymbolicLinks: false, | ||
| }).catch((error: unknown) => { | ||
| const code = (error as NodeJS.ErrnoException | { code?: string })?.code; | ||
| if (code === 'EPERM' || code === 'EACCES') { | ||
| throw new PermissionError( | ||
| `Permission denied while scanning directory. Please check folder access permissions for your terminal app. path: ${rootDir}`, | ||
| rootDir, | ||
| ); |
There was a problem hiding this comment.
The globby call and its associated error handling logic are duplicated here and in the if (explicitFiles) block (lines 219-239). This repetition can be avoided by creating a reusable helper function for running globby with the common options and error handling. This would make the code cleaner and easier to maintain.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #931 +/- ##
==========================================
- Coverage 89.50% 89.35% -0.16%
==========================================
Files 111 111
Lines 7807 7865 +58
Branches 1498 1507 +9
==========================================
+ Hits 6988 7028 +40
- Misses 819 837 +18 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
PR Review: Fix stdin mode hang on macOSSummaryThis PR effectively resolves issue #903 by bypassing globby when processing explicit files in stdin mode. The root cause analysis is solid, and the solution is pragmatic. The implementation separates concerns between globby (for include patterns) and manual filtering (for explicit files). ✅ Strengths
🔍 Code Quality & Best PracticesPositive Aspects:
Minor Suggestions:
|
There was a problem hiding this comment.
Actionable comments posted: 3
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/core/file/fileSearch.ts(1 hunks)tests/core/file/fileSearch.test.ts(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
tests/core/file/fileSearch.test.ts (2)
src/core/file/fileSearch.ts (1)
searchFiles(96-329)tests/testing/testUtils.ts (1)
createMockConfig(15-45)
src/core/file/fileSearch.ts (2)
src/shared/logger.ts (2)
logger(89-89)error(34-38)src/core/file/permissionCheck.ts (1)
PermissionError(16-25)
🪛 GitHub Actions: CI
tests/core/file/fileSearch.test.ts
[error] 598-598: Test failure due to Windows path separators: expected POSIX-style paths (e.g., 'src/file1.ts') but received Windows-style paths (e.g., 'src\file1.ts') in result.filePaths.
[error] 625-625: Test assertion failed: expected file paths to be ['lib/utils.ts', 'src/main.ts'] but received ['lib\utils.ts', 'src\main.ts'] (Windows path separators).
[error] 667-667: Test assertion failed: expected filePaths length to be 3 but got 4 due to duplicate handling with mixed path separators in stdin mode.
🪛 GitHub Check: Test with Bun (windows-latest, latest)
tests/core/file/fileSearch.test.ts
[failure] 598-598: tests/core/file/fileSearch.test.ts > fileSearch > searchFiles path validation > should filter explicit files based on include and ignore patterns
AssertionError: expected [ Array(4) ] to deeply equal ArrayContaining{…}
- Expected
- Received
- ArrayContaining [
- "src/file1.ts",
- "src/file2.js",
- "src/file3.ts",
- [
- "src\file1.ts",
- "src\file2.js",
- "src\file3.ts",
"src/other.ts",
]
❯ tests/core/file/fileSearch.test.ts:598:32
[failure] 667-667: tests/core/file/fileSearch.test.ts > fileSearch > searchFiles path validation > should merge globby results and explicit files in stdin mode
AssertionError: expected [ 'src\duplicate.ts', …(3) ] to have a length of 3 but got 4
- Expected
- Received
- 3
- 4
❯ tests/core/file/fileSearch.test.ts:667:32
[failure] 625-625: tests/core/file/fileSearch.test.ts > fileSearch > searchFiles path validation > should handle explicit files with ignore patterns only
AssertionError: expected [ 'lib\utils.ts', 'src\main.ts' ] to deeply equal [ 'lib/utils.ts', 'src/main.ts' ]
- Expected
-
Received
[
- "lib/utils.ts",
- "src/main.ts",
- "lib\utils.ts",
- "src\main.ts",
]
❯ tests/core/file/fileSearch.test.ts:625:32
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: claude-review
| if (config.ignore.useGitignore) { | ||
| // Read .gitignore from root directory | ||
| const gitignorePath = path.join(rootDir, '.gitignore'); | ||
| try { | ||
| const gitignoreContent = await fs.readFile(gitignorePath, 'utf8'); | ||
| const gitignorePatterns = parseIgnoreContent(gitignoreContent); | ||
| allIgnorePatterns.push(...gitignorePatterns); | ||
| logger.trace('Loaded .gitignore patterns:', gitignorePatterns); | ||
| } catch (error) { | ||
| // .gitignore might not exist, which is fine | ||
| logger.trace( | ||
| 'No .gitignore found or could not read:', | ||
| error instanceof Error ? error.message : String(error), | ||
| ); | ||
| } | ||
|
|
||
| // If no include patterns at all, default to all files | ||
| if (includePatterns.length === 0) { | ||
| includePatterns = ['**/*']; | ||
| } | ||
| // Read .repomixignore from root directory | ||
| const repomixignorePath = path.join(rootDir, '.repomixignore'); | ||
| try { | ||
| const repomixignoreContent = await fs.readFile(repomixignorePath, 'utf8'); | ||
| const repomixignorePatterns = parseIgnoreContent(repomixignoreContent); | ||
| allIgnorePatterns.push(...repomixignorePatterns); | ||
| logger.trace('Loaded .repomixignore patterns:', repomixignorePatterns); | ||
| } catch (error) { | ||
| // .repomixignore might not exist, which is fine | ||
| logger.trace( | ||
| 'No .repomixignore found or could not read:', | ||
| error instanceof Error ? error.message : String(error), | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
Always load .repomixignore in stdin mode
In stdin mode we now skip .repomixignore whenever useGitignore is false, but normal mode still respects it because getIgnoreFilePatterns always includes .repomixignore. This regresses documented behaviour: disabling gitignore handling should not disable the Repomix-specific ignore file. Please load .repomixignore independently of the gitignore flag so explicit files keep honoring those rules.
- if (config.ignore.useGitignore) {
- // Read .gitignore from root directory
- const gitignorePath = path.join(rootDir, '.gitignore');
- try {
- const gitignoreContent = await fs.readFile(gitignorePath, 'utf8');
- const gitignorePatterns = parseIgnoreContent(gitignoreContent);
- allIgnorePatterns.push(...gitignorePatterns);
- logger.trace('Loaded .gitignore patterns:', gitignorePatterns);
- } catch (error) {
- // .gitignore might not exist, which is fine
- logger.trace(
- 'No .gitignore found or could not read:',
- error instanceof Error ? error.message : String(error),
- );
- }
-
- // Read .repomixignore from root directory
- const repomixignorePath = path.join(rootDir, '.repomixignore');
- try {
- const repomixignoreContent = await fs.readFile(repomixignorePath, 'utf8');
- const repomixignorePatterns = parseIgnoreContent(repomixignoreContent);
- allIgnorePatterns.push(...repomixignorePatterns);
- logger.trace('Loaded .repomixignore patterns:', repomixignorePatterns);
- } catch (error) {
- // .repomixignore might not exist, which is fine
- logger.trace(
- 'No .repomixignore found or could not read:',
- error instanceof Error ? error.message : String(error),
- );
- }
- }
+ const loadIgnoreFile = async (fileName: string, enabled: boolean) => {
+ if (!enabled) return;
+ const filePath = path.join(rootDir, fileName);
+ try {
+ const content = await fs.readFile(filePath, 'utf8');
+ const patterns = parseIgnoreContent(content).map(normalizeGlobPattern);
+ allIgnorePatterns.push(...patterns);
+ logger.trace(`Loaded ${fileName} patterns:`, patterns);
+ } catch (error) {
+ logger.trace(
+ `No ${fileName} found or could not read:`,
+ error instanceof Error ? error.message : String(error),
+ );
+ }
+ };
+
+ await loadIgnoreFile('.gitignore', config.ignore.useGitignore);
+ await loadIgnoreFile('.repomixignore', true);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/core/file/fileSearch.ts around lines 188 to 218, the code currently only
reads .repomixignore when config.ignore.useGitignore is true, which causes
.repomixignore to be skipped in stdin mode when gitignore handling is disabled;
move or duplicate the .repomixignore read/parse/log block so it runs
unconditionally (outside or after the if (config.ignore.useGitignore) block) and
keep the same try/catch behavior and logger.trace messages to handle a missing
file gracefully.
| const gitignoreContent = await fs.readFile(gitignorePath, 'utf8'); | ||
| const gitignorePatterns = parseIgnoreContent(gitignoreContent); | ||
| allIgnorePatterns.push(...gitignorePatterns); | ||
| logger.trace('Loaded .gitignore patterns:', gitignorePatterns); | ||
| } catch (error) { | ||
| // .gitignore might not exist, which is fine | ||
| logger.trace( | ||
| 'No .gitignore found or could not read:', | ||
| error instanceof Error ? error.message : String(error), | ||
| ); | ||
| } | ||
|
|
||
| // If no include patterns at all, default to all files | ||
| if (includePatterns.length === 0) { | ||
| includePatterns = ['**/*']; | ||
| } | ||
| // Read .repomixignore from root directory | ||
| const repomixignorePath = path.join(rootDir, '.repomixignore'); | ||
| try { | ||
| const repomixignoreContent = await fs.readFile(repomixignorePath, 'utf8'); | ||
| const repomixignorePatterns = parseIgnoreContent(repomixignoreContent); | ||
| allIgnorePatterns.push(...repomixignorePatterns); | ||
| logger.trace('Loaded .repomixignore patterns:', repomixignorePatterns); |
There was a problem hiding this comment.
Normalize patterns loaded from ignore files
Lines like build/ or tmp/ are extremely common in .gitignore, but when we feed them straight into minimatch they no longer behave like git (they won't exclude build/index.js). We already normalize config-derived patterns; we need to apply the same normalizeGlobPattern to patterns pulled from .gitignore / .repomixignore before pushing them into allIgnorePatterns, otherwise stdin-mode files slip through those ignores.
- const gitignorePatterns = parseIgnoreContent(gitignoreContent);
+ const gitignorePatterns = parseIgnoreContent(gitignoreContent).map(normalizeGlobPattern);
allIgnorePatterns.push(...gitignorePatterns);
...
- const repomixignorePatterns = parseIgnoreContent(repomixignoreContent);
+ const repomixignorePatterns = parseIgnoreContent(repomixignoreContent).map(normalizeGlobPattern);
allIgnorePatterns.push(...repomixignorePatterns);🤖 Prompt for AI Agents
In src/core/file/fileSearch.ts around lines 192 to 210, the patterns read from
.gitignore and .repomixignore must be normalized before being added to
allIgnorePatterns; map the arrays returned by parseIgnoreContent through the
existing normalizeGlobPattern function (ensuring normalizeGlobPattern is
imported/available) and push the normalized results into allIgnorePatterns, and
update the logger.trace calls to log the normalized patterns so ignores behave
like git (e.g., "build/" will also match build/index.js).
| // 1. Run globby only for includePatterns (if any) | ||
| const globbyPatterns = includePatterns.length > 0 ? includePatterns : []; | ||
| const globbyResults = | ||
| globbyPatterns.length > 0 | ||
| ? await globby(globbyPatterns, { | ||
| cwd: rootDir, | ||
| ignore: [...adjustedIgnorePatterns], | ||
| ignoreFiles: [...ignoreFilePatterns], | ||
| onlyFiles: true, | ||
| absolute: false, | ||
| dot: true, | ||
| followSymbolicLinks: false, | ||
| }).catch((error: unknown) => { | ||
| const code = (error as NodeJS.ErrnoException | { code?: string })?.code; | ||
| if (code === 'EPERM' || code === 'EACCES') { | ||
| throw new PermissionError( | ||
| `Permission denied while scanning directory. Please check folder access permissions for your terminal app. path: ${rootDir}`, | ||
| rootDir, | ||
| ); | ||
| } | ||
| throw error; | ||
| }) | ||
| : []; | ||
|
|
||
| logger.trace('Globby results for includePatterns:', globbyResults); | ||
|
|
||
| // 2. Convert explicit files to relative paths | ||
| const relativePaths = explicitFiles.map((filePath) => path.relative(rootDir, filePath)); | ||
|
|
||
| logger.trace('Explicit files (relative):', relativePaths); | ||
|
|
||
| // 3. Filter explicit files using ignore patterns (manually with minimatch) | ||
| const filteredExplicitFiles = relativePaths.filter((filePath) => { | ||
| // Check if file matches any ignore pattern | ||
| const shouldIgnore = allIgnorePatterns.some( | ||
| (pattern) => minimatch(filePath, pattern) || minimatch(`${filePath}/`, pattern), | ||
| ); | ||
| } | ||
| throw error; | ||
| }); | ||
| return !shouldIgnore; | ||
| }); | ||
|
|
||
| logger.trace('Filtered explicit files:', filteredExplicitFiles); | ||
|
|
||
| // 4. Merge globby results and filtered explicit files, removing duplicates | ||
| filePaths = [...new Set([...globbyResults, ...filteredExplicitFiles])]; | ||
|
|
||
| logger.trace('Merged file paths:', filePaths); | ||
| } else { | ||
| // Normal mode: use globby with all patterns | ||
| const patterns = includePatterns.length > 0 ? includePatterns : ['**/*']; |
There was a problem hiding this comment.
Normalize explicit paths to POSIX separators before merging
On Windows, path.relative returns backslash-separated paths, so filteredExplicitFiles ends up with values like src\file1.ts while globbyResults uses src/file1.ts. This breaks deduplication and causes ignore checks to misbehave, which is exactly what the Windows CI failures are showing (mixed separators and duplicate entries). Convert both the globby output and the explicit-path relatives to POSIX separators before combining them so we return a consistent, de-duped list everywhere.
- const globbyResults =
- globbyPatterns.length > 0
- ? await globby(globbyPatterns, {
+ const toPosix = (value: string) => value.replace(/\\/g, '/');
+ const globbyResults =
+ globbyPatterns.length > 0
+ ? (
+ await globby(globbyPatterns, {
cwd: rootDir,
ignore: [...adjustedIgnorePatterns],
ignoreFiles: [...ignoreFilePatterns],
onlyFiles: true,
absolute: false,
dot: true,
followSymbolicLinks: false,
}).catch((error: unknown) => {
const code = (error as NodeJS.ErrnoException | { code?: string })?.code;
if (code === 'EPERM' || code === 'EACCES') {
throw new PermissionError(
`Permission denied while scanning directory. Please check folder access permissions for your terminal app. path: ${rootDir}`,
rootDir,
);
}
throw error;
})
- : [];
+ ).map(toPosix)
+ : [];
...
- const relativePaths = explicitFiles.map((filePath) => path.relative(rootDir, filePath));
+ const relativePaths = explicitFiles.map((filePath) => toPosix(path.relative(rootDir, filePath)));🤖 Prompt for AI Agents
In src/core/file/fileSearch.ts around lines 220 to 268, explicit file paths
produced by path.relative are using Windows backslashes which mismatch globby's
POSIX-style results and break deduplication and ignore checks; normalize both
the globbyResults and the relativePaths to POSIX separators before running
minimatch, filtering, and merging. Update the code to map globbyResults and
explicitFiles -> relativePaths through a function that replaces backslashes with
'/' (or use path.posix) so both sets use forward-slash separators, then run the
ignore filtering and dedupe on those normalized values and continue with the
rest of the logic.
Fixes #903
Summary
When using
--stdinwith explicit file paths (e.g.,git ls-files | repomix --stdin), the application could hang on macOS when.gitignorefiles were present in the explicit file list.Root Cause
globby tried to use
.gitignoreas both:ignoreFilesparameter)includePatterns)This created a pathological state causing hangs on macOS.
Solution
Bypass globby for explicit files in stdin mode:
includePatterns(if configured).gitignorefrom root directoryThis ensures:
.gitignorerules are respected in stdin mode.gitignorein the file listincludePatternswork correctly alongside stdin filesLimitations
.gitignore(subdirectory.gitignorefiles not supported in stdin mode)git ls-filesalready respects all.gitignorefilesChanges
.gitignorerules applied to stdin filesTesting
Unit Tests
tests/core/file/fileSearch.test.tsManual Testing
Performance Test
Review Focus
.gitignorerules are correctly applied to stdin files.gitignorein file listincludePatternsand stdin files merge correctly.gitignorefiles are not supported (limitation documented)Checklist
npm run testnpm run lint