From e5b389cdb16ed63f7116e613a4abb1d1b8851e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Sun, 15 Mar 2026 11:39:47 -0600 Subject: [PATCH] fix: handle EPERM/EACCES in scanForLeakedPaths on Windows (#1055) Wrap readdirSync and readFileSync calls in try/catch to silently skip directories and files with restricted ACLs (e.g. Chrome/Gemini certificate stores on Windows) instead of crashing the installer. Closes #964 Co-authored-by: Claude Opus 4.6 (1M context) --- bin/install.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/bin/install.js b/bin/install.js index 578bd4b61..cc8dbf0ed 100755 --- a/bin/install.js +++ b/bin/install.js @@ -2432,13 +2432,29 @@ function install(isGlobal, runtime = 'claude') { const leakedPaths = []; function scanForLeakedPaths(dir) { if (!fs.existsSync(dir)) return; - const entries = fs.readdirSync(dir, { withFileTypes: true }); + let entries; + try { + entries = fs.readdirSync(dir, { withFileTypes: true }); + } catch (err) { + if (err.code === 'EPERM' || err.code === 'EACCES') { + return; // skip inaccessible directories + } + throw err; + } for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { scanForLeakedPaths(fullPath); } else if ((entry.name.endsWith('.md') || entry.name.endsWith('.toml')) && entry.name !== 'CHANGELOG.md') { - const content = fs.readFileSync(fullPath, 'utf8'); + let content; + try { + content = fs.readFileSync(fullPath, 'utf8'); + } catch (err) { + if (err.code === 'EPERM' || err.code === 'EACCES') { + continue; // skip inaccessible files + } + throw err; + } const matches = content.match(/(?:~|\$HOME)\/\.claude\b/g); if (matches) { leakedPaths.push({ file: fullPath.replace(targetDir + '/', ''), count: matches.length });