( subdir: ClaudeConfigDirectory, cwd: string, )
| 232 | * @returns Array of directory paths containing .claude/subdir, from most specific (cwd) to least specific |
| 233 | */ |
| 234 | export function getProjectDirsUpToHome( |
| 235 | subdir: ClaudeConfigDirectory, |
| 236 | cwd: string, |
| 237 | ): string[] { |
| 238 | const home = resolve(homedir()).normalize('NFC') |
| 239 | const gitRoot = resolveStopBoundary(cwd) |
| 240 | let current = resolve(cwd) |
| 241 | const dirs: string[] = [] |
| 242 | |
| 243 | // Traverse from current directory up to git root (or home if not in a git repo) |
| 244 | while (true) { |
| 245 | // Stop if we've reached the home directory (don't check it, as it's loaded separately as userDir) |
| 246 | // Use normalized comparison to handle Windows drive letter casing (C:\ vs c:\) |
| 247 | if ( |
| 248 | normalizePathForComparison(current) === normalizePathForComparison(home) |
| 249 | ) { |
| 250 | break |
| 251 | } |
| 252 | |
| 253 | const claudeSubdir = join(current, '.claude', subdir) |
| 254 | // Filter to existing dirs. This is a perf filter (avoids spawning |
| 255 | // ripgrep on non-existent dirs downstream) and the worktree fallback |
| 256 | // in loadMarkdownFilesForSubdir relies on it. statSync + explicit error |
| 257 | // handling instead of existsSync — re-throws unexpected errors rather |
| 258 | // than silently swallowing them. Downstream loadMarkdownFiles handles |
| 259 | // the TOCTOU window (dir disappearing before read) gracefully. |
| 260 | try { |
| 261 | statSync(claudeSubdir) |
| 262 | dirs.push(claudeSubdir) |
| 263 | } catch (e: unknown) { |
| 264 | if (!isFsInaccessible(e)) throw e |
| 265 | } |
| 266 | |
| 267 | // Stop after processing the git root directory - this prevents commands from parent |
| 268 | // directories outside the repository from appearing in the project |
| 269 | if ( |
| 270 | gitRoot && |
| 271 | normalizePathForComparison(current) === |
| 272 | normalizePathForComparison(gitRoot) |
| 273 | ) { |
| 274 | break |
| 275 | } |
| 276 | |
| 277 | // Move to parent directory |
| 278 | const parent = dirname(current) |
| 279 | |
| 280 | // Safety check: if parent is the same as current, we've reached the root |
| 281 | if (parent === current) { |
| 282 | break |
| 283 | } |
| 284 | |
| 285 | current = parent |
| 286 | } |
| 287 | |
| 288 | return dirs |
| 289 | } |
| 290 | |
| 291 | /** |
no test coverage detected