@@ -256,6 +256,34 @@ import Testing
256256 #expect( leaked. isEmpty, " walk() returned non-symlink URLs that physically resolve outside the context: \( leaked) " )
257257 }
258258
259+ // MARK: - walk(): plain subdirectory contents under a symlinked ancestor
260+ //
261+ // Globber.children(of:) used to list directories via
262+ // contentsOfDirectory(at:), which silently resolves /tmp to /private/tmp
263+ // even for a non-symlink child. That made walk()'s parentOf() check
264+ // reject every entry found by descending into a plain subdirectory, so
265+ // "COPY foodir /dest" tarred an empty directory while
266+ // "COPY foodir/payload /dest/payload" worked fine.
267+
268+ @Test func testWalkUnderSymlinkedAncestor( ) async throws {
269+ // Root the context directly under /tmp (a symlink to /private/tmp on
270+ // macOS) to reproduce the bug precisely.
271+ let tmpBase = URL ( fileURLWithPath: " /tmp " ) . appendingPathComponent ( UUID ( ) . uuidString)
272+ let ctx = tmpBase. appendingPathComponent ( " context " )
273+ try fm. createDirectory ( at: ctx, withIntermediateDirectories: true )
274+ defer { try ? fm. removeItem ( at: tmpBase) }
275+
276+ let subdir = ctx. appendingPathComponent ( " foodir " )
277+ try fm. createDirectory ( at: subdir, withIntermediateDirectories: true )
278+ try write ( " payload " , to: subdir. appendingPathComponent ( " payload.txt " ) )
279+
280+ let fssync = try BuildFSSync ( ctx)
281+ let infos = try await walkJSON ( fssync, followPaths: [ " foodir " ] )
282+
283+ let payload = infos. first { $0. name == " foodir/payload.txt " }
284+ #expect( payload != nil , " expected foodir/payload.txt to be included when COPYing a directory glob, got: \( infos. map { $0. name } ) " )
285+ }
286+
259287 // MARK: - walk(): JSON/FileInfo metadata paths
260288 //
261289 // walk() has two response formats: a tar stream (the primary data path,
0 commit comments