(currentDir: string)
| 456 | const visitedDirs = new Set<string>() |
| 457 | |
| 458 | async function walk(currentDir: string): Promise<void> { |
| 459 | if (signal.aborted) { |
| 460 | return |
| 461 | } |
| 462 | |
| 463 | // Cycle detection: track visited directories by device+inode |
| 464 | // Uses bigint: true to handle filesystems with large inodes (e.g., ExFAT) |
| 465 | // that exceed JavaScript's Number precision (53 bits). |
| 466 | // See: https://github.com/anthropics/claude-code/issues/13893 |
| 467 | try { |
| 468 | const stats = await stat(currentDir, { bigint: true }) |
| 469 | if (stats.isDirectory()) { |
| 470 | const dirKey = |
| 471 | stats.dev !== undefined && stats.ino !== undefined |
| 472 | ? `${stats.dev}:${stats.ino}` // Unix/Linux: device + inode |
| 473 | : await realpath(currentDir) // Windows: canonical path |
| 474 | |
| 475 | if (visitedDirs.has(dirKey)) { |
| 476 | logForDebugging( |
| 477 | `Skipping already visited directory (circular symlink): ${currentDir}`, |
| 478 | ) |
| 479 | return |
| 480 | } |
| 481 | visitedDirs.add(dirKey) |
| 482 | } |
| 483 | } catch (error) { |
| 484 | const errorMessage = |
| 485 | error instanceof Error ? error.message : String(error) |
| 486 | logForDebugging(`Failed to stat directory ${currentDir}: ${errorMessage}`) |
| 487 | return |
| 488 | } |
| 489 | |
| 490 | try { |
| 491 | const entries = await readdir(currentDir, { withFileTypes: true }) |
| 492 | |
| 493 | for (const entry of entries) { |
| 494 | if (signal.aborted) { |
| 495 | break |
| 496 | } |
| 497 | |
| 498 | const fullPath = join(currentDir, entry.name) |
| 499 | |
| 500 | try { |
| 501 | // Handle symlinks: isFile() and isDirectory() return false for symlinks |
| 502 | if (entry.isSymbolicLink()) { |
| 503 | try { |
| 504 | const stats = await stat(fullPath) // stat() follows symlinks |
| 505 | if (stats.isDirectory()) { |
| 506 | await walk(fullPath) |
| 507 | } else if (stats.isFile() && entry.name.endsWith('.md')) { |
| 508 | files.push(fullPath) |
| 509 | } |
| 510 | } catch (error) { |
| 511 | const errorMessage = |
| 512 | error instanceof Error ? error.message : String(error) |
| 513 | logForDebugging( |
| 514 | `Failed to follow symlink ${fullPath}: ${errorMessage}`, |
| 515 | ) |
no test coverage detected