MCPcopy Index your code
hub / github.com/codeaashu/claude-code / collectFilesForZip

Function collectFilesForZip

src/utils/plugins/zipCache.ts:235–323  ·  view source on GitHub ↗

* Recursively collect files from a directory for zipping. * Uses lstat to detect symlinks and tracks visited inodes for cycle detection.

(
  baseDir: string,
  relativePath: string,
  files: Record<string, ZipEntry>,
  visited: Set<string>,
)

Source from the content-addressed store, hash-verified

233 * Uses lstat to detect symlinks and tracks visited inodes for cycle detection.
234 */
235async function collectFilesForZip(
236 baseDir: string,
237 relativePath: string,
238 files: Record<string, ZipEntry>,
239 visited: Set<string>,
240): Promise<void> {
241 const currentDir = relativePath ? join(baseDir, relativePath) : baseDir
242 let entries: string[]
243 try {
244 entries = await readdir(currentDir)
245 } catch {
246 return
247 }
248
249 // Track visited directories by dev+ino to detect symlink cycles.
250 // bigint: true is required — on Windows NTFS, the file index packs a 16-bit
251 // sequence number into the high bits. Once that sequence exceeds ~32 (very
252 // common on a busy CI runner that churns through temp files), the value
253 // exceeds Number.MAX_SAFE_INTEGER and two adjacent directories round to the
254 // same JS number, causing subdirs to be silently skipped as "cycles". This
255 // broke the round-trip test on Windows CI when sharding shuffled which tests
256 // ran first and pushed MFT sequence numbers over the precision cliff.
257 // See also: markdownConfigLoader.ts getFileIdentity, anthropics/claude-code#13893
258 try {
259 const dirStat = await stat(currentDir, { bigint: true })
260 // ReFS (Dev Drive), NFS, some FUSE mounts report dev=0 and ino=0 for
261 // everything. Fail open: skip cycle detection rather than skip the
262 // directory. We already skip symlinked directories unconditionally below,
263 // so the only cycle left here is a bind mount, which we accept.
264 if (dirStat.dev !== 0n || dirStat.ino !== 0n) {
265 const key = `${dirStat.dev}:${dirStat.ino}`
266 if (visited.has(key)) {
267 logForDebugging(`Skipping symlink cycle at ${currentDir}`)
268 return
269 }
270 visited.add(key)
271 }
272 } catch {
273 return
274 }
275
276 for (const entry of entries) {
277 // Skip hidden files that are git-related
278 if (entry === '.git') {
279 continue
280 }
281
282 const fullPath = join(currentDir, entry)
283 const relPath = relativePath ? `${relativePath}/${entry}` : entry
284
285 let fileStat
286 try {
287 fileStat = await lstat(fullPath)
288 } catch {
289 continue
290 }
291
292 // Skip symlinked directories (follow symlinked files)

Callers 1

createZipFromDirectoryFunction · 0.85

Calls 6

readdirFunction · 0.85
statFunction · 0.85
logForDebuggingFunction · 0.85
readFileFunction · 0.85
hasMethod · 0.45
addMethod · 0.45

Tested by

no test coverage detected