( includeInvalid: boolean, )
| 662 | * the workspace directory does not match the cwd) |
| 663 | */ |
| 664 | export async function detectIDEs( |
| 665 | includeInvalid: boolean, |
| 666 | ): Promise<DetectedIDEInfo[]> { |
| 667 | const detectedIDEs: DetectedIDEInfo[] = [] |
| 668 | |
| 669 | try { |
| 670 | // Get the CLAUDE_CODE_SSE_PORT if set |
| 671 | const ssePort = process.env.CLAUDE_CODE_SSE_PORT |
| 672 | const envPort = ssePort ? parseInt(ssePort) : null |
| 673 | |
| 674 | // Get the current working directory, normalized to NFC for consistent |
| 675 | // comparison. macOS returns NFD paths (decomposed Unicode), while IDEs |
| 676 | // like VS Code report NFC paths (composed Unicode). Without normalization, |
| 677 | // paths containing accented/CJK characters fail to match. |
| 678 | const cwd = getOriginalCwd().normalize('NFC') |
| 679 | |
| 680 | // Get sorted lockfiles (full paths) and read them all in parallel. |
| 681 | // findAvailableIDE() polls this every 1s for up to 30s; serial I/O here was |
| 682 | // showing up as ~500ms self-time in CPU profiles. |
| 683 | const lockfiles = await getSortedIdeLockfiles() |
| 684 | const lockfileInfos = await Promise.all(lockfiles.map(readIdeLockfile)) |
| 685 | |
| 686 | // Ancestor PID walk shells out (ps in a loop, up to 10x). Make it lazy and |
| 687 | // single-shot per detectIDEs() call; with the workspace-check-first ordering |
| 688 | // below, this often never fires at all. |
| 689 | const getAncestors = makeAncestorPidLookup() |
| 690 | const needsAncestryCheck = getPlatform() !== 'wsl' && isSupportedTerminal() |
| 691 | |
| 692 | // Try to find a lockfile that contains our current working directory |
| 693 | for (const lockfileInfo of lockfileInfos) { |
| 694 | if (!lockfileInfo) continue |
| 695 | |
| 696 | let isValid = false |
| 697 | if (isEnvTruthy(process.env.CLAUDE_CODE_IDE_SKIP_VALID_CHECK)) { |
| 698 | isValid = true |
| 699 | } else if (lockfileInfo.port === envPort) { |
| 700 | // If the port matches the environment variable, mark as valid regardless of directory |
| 701 | isValid = true |
| 702 | } else { |
| 703 | // Otherwise, check if the current working directory is within the workspace folders |
| 704 | isValid = lockfileInfo.workspaceFolders.some(idePath => { |
| 705 | if (!idePath) return false |
| 706 | |
| 707 | let localPath = idePath |
| 708 | |
| 709 | // Handle WSL-specific path conversion and distro matching |
| 710 | if ( |
| 711 | getPlatform() === 'wsl' && |
| 712 | lockfileInfo.runningInWindows && |
| 713 | process.env.WSL_DISTRO_NAME |
| 714 | ) { |
| 715 | // Check for WSL distro mismatch |
| 716 | if (!checkWSLDistroMatch(idePath, process.env.WSL_DISTRO_NAME)) { |
| 717 | return false |
| 718 | } |
| 719 | |
| 720 | // Try both the original path and the converted path |
| 721 | // This handles cases where the IDE might report either format |
no test coverage detected