From 66d7aa736ca82531c0938d1a95c054d65dac4fbd Mon Sep 17 00:00:00 2001 From: David Snelling Date: Sat, 31 Jan 2026 09:09:12 -0800 Subject: [PATCH] fix: invalidate VFS caches recursively on rmdir to prevent orphaned reads When rmdir({ recursive: true }) deleted a directory tree, child file paths remained in contentCache and statCache, causing readFile() to return stale data for deleted files. Adds recursive flag to invalidateCaches() that evicts all descendant keys by prefix. --- src/vfs/VirtualFileSystem.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/vfs/VirtualFileSystem.ts b/src/vfs/VirtualFileSystem.ts index 9142e2ac..a7b220e2 100644 --- a/src/vfs/VirtualFileSystem.ts +++ b/src/vfs/VirtualFileSystem.ts @@ -979,7 +979,7 @@ export class VirtualFileSystem implements IVirtualFileSystem { // Invalidate caches (recursive invalidation handles all descendants) this.pathResolver.invalidatePath(path, true) - this.invalidateCaches(path) + this.invalidateCaches(path, true) // Trigger watchers this.triggerWatchers(path, 'rename') @@ -1424,9 +1424,23 @@ export class VirtualFileSystem implements IVirtualFileSystem { return new RegExp(`^${regex}$`).test(name) } - private invalidateCaches(path: string): void { + private invalidateCaches(path: string, recursive = false): void { this.contentCache.delete(path) this.statCache.delete(path) + + if (recursive) { + const prefix = path.endsWith('/') ? path : path + '/' + for (const cachedPath of this.contentCache.keys()) { + if (cachedPath.startsWith(prefix)) { + this.contentCache.delete(cachedPath) + } + } + for (const cachedPath of this.statCache.keys()) { + if (cachedPath.startsWith(prefix)) { + this.statCache.delete(cachedPath) + } + } + } } private triggerWatchers(path: string, event: 'rename' | 'change'): void {