Welcome to the fourth chapter of our File Persistence tutorial!
In the previous chapter, Persistence Orchestration, we set up the "Project Manager" who oversees the entire saving process. Now, we dive into the specific task of the worker: Finding the right files.
Imagine you have a project folder with 1,000 files. You just asked Claude to create one new Python script.
When the session ends, we want to save that new script to the cloud. We have two options:
We choose the smart way. This process is called Delta Scanning.
Think of this scanner like a motion-activated security camera.
The scanner looks at the file system and asks a simple question for every file:
"Were you touched since the camera turned on?"
If the answer is Yes, we save it. If the answer is No, we ignore it.
mtime Comparison
Computers stamp every file with a "Modified Time" (called mtime).
To implement Delta Scanning, we need two pieces of data:
turnStartTime: The exact millisecond the user started the interaction.file.mtimeMs: The exact millisecond the file was last changed.The Logic:
IF (File Modification Time >= Turn Start Time)
-> SAVE FILE
ELSE
-> IGNORE FILE
Let's visualize the process inside the scanner code.
This logic lives in outputsScanner.ts. The main function is called findModifiedFiles.
Let's break down the code into small, digestible steps.
First, we need to get a list of everything in the folder. We use the recursive: true option so we can find files hiding inside sub-folders.
// From outputsScanner.ts
export async function findModifiedFiles(turnStartTime, outputsDir) {
// 1. Get all file entries recursively
const entries = await fs.readdir(outputsDir, {
withFileTypes: true,
recursive: true,
})
Explanation:
fs.readdir acts like opening a file explorer window. It gives us a list of every item in the outputsDir.
Not everything in a folder is a regular file. Sometimes there are "Symbolic Links" (shortcuts to other places). For security reasons, we skip those immediately.
const filePaths: string[] = []
for (const entry of entries) {
// Skip shortcuts (symlinks) for safety
if (entry.isSymbolicLink()) continue
// If it's a real file, keep it for checking
if (entry.isFile()) {
filePaths.push(path.join(entry.parentPath, entry.name))
}
}
Explanation:
We loop through the list. If it's a standard file, we build its full path (e.g., /home/user/project/script.py) and add it to our "To Check" list.
Now comes the most important part. We check the mtime of each candidate.
Note: In the actual code, we use Promise.all to check many files at once for speed, but the logic remains simple:
// Check the stats (details) of the file
const stat = await fs.lstat(filePath)
// THE GOLDEN RULE:
// Is the modification time >= the start time?
if (stat.mtimeMs >= turnStartTime) {
modifiedFiles.push(filePath)
}
Explanation:
We look at stat.mtimeMs. If it is greater than (newer) or equal to our turnStartTime, it qualifies as a "Delta." It gets added to the final list.
File systems change fast. A file might exist when we list the directory (Step 1) but be deleted by the time we check its stats (Step 3).
To prevent the system from crashing, we wrap the check in a try/catch.
try {
const stat = await fs.lstat(filePath)
// ... check time logic ...
} catch {
// If the file was deleted mid-scan, just ignore it.
return null
}
Explanation: This makes the scanner robust. If a file disappears "ghost-style" in the middle of the operation, the scanner quietly moves on to the next one.
Back in filePersistence.ts (the Orchestrator from Chapter 3), we use this scanner like this:
// From filePersistence.ts
// 1. Run the scanner
const modifiedFiles = await findModifiedFiles(turnStartTime, outputsDir)
// 2. If nothing changed, we can stop early!
if (modifiedFiles.length === 0) {
logDebug('No modified files to persist')
return
}
This efficiency is crucial. Most of the time, the user might just ask a question ("How does this code work?") without creating files. In those cases, modifiedFiles is empty, and the system does no work.
You have learned how Delta Scanning acts as an intelligent filter.
mtime against the turnStartTime.However, just because a file is new doesn't mean it is safe to upload. What if the scanner found a file that is technically "new" but located in a forbidden system folder due to a relative path trick?
In the final chapter, we will learn how to clean and verify this list before the final upload.
Next Chapter: Security Sanitization
Generated by Code IQ