fix(file-handler): delete-db no-ops when the storage file is already gone, resurrecting deleted files#56
Open
cosmic-fire-eng wants to merge 1 commit into
Conversation
…dy-removed file In the offline-scanner delete-db path a file that is already gone from storage is never tombstoned in the database, so the next scan resurrects it. With headless or CLI synchronisation this shows up as deletions silently reappearing and no tombstone ever being written. deleteFileFromDB() calls infoToStub() first, which stats storage to build the stub. In the delete-db branch the file is by definition already gone from storage, so the stub is always null and the method returns false without touching the database. The offline scanner then ignored that return value and cleared the file's last-seen entry from the status map regardless. On the following scan the document looks database-only with no last-seen record, is classified as update-storage, and the deleted file is written back from the database. Two small, independent parts: 1. deleteFileFromDB: when the storage stub is null, fall back to a path-based database delete. Fetch the entry by path and, when it exists and is not already deleted, delete it by path. This mirrors what the CLI rm command already does. It still returns false when there is genuinely nothing to delete. 2. offlineScanner: only clear the fileStatusMap last-seen record when the database delete actually succeeded. When it returns false, keep the record and log a notice, so a no-op delete can never feed the resurrection path. Adds a focused unit test covering the false-return path: the last-seen record is retained and the file is not resurrected. Co-Authored-By: Claude <noreply@anthropic.com>
vrtmrz
reviewed
Jun 23, 2026
| this._log(`File ${tryGetFilePath(info)} is not exist on the storage`, LOG_LEVEL_VERBOSE); | ||
| return false; | ||
| } | ||
| const entryByPath = await this.db.fetchEntry(path as FilePathWithPrefix, undefined, true, true); |
Owner
There was a problem hiding this comment.
If we are not going to use the contents, I think checking with fetchEntryMeta is another option, as it is lighter and does not involve a network request. Is this intentional?
Alternatively, if the idea is that the file should only be manipulated when all the chunks are present, that makes sense too.
The implementation in the CLI is a bit more naive (Given that it is a CLI, this is actually quite good in its own way), though. Of course, your PR's style is better in the plug-in.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
In the offline-scanner
delete-dbpath, a file that has already been removed from storage is never tombstoned in the database, so the next scan resurrects it. With headless / CLI synchronisation this shows up as deletions silently reappearing and zero tombstones ever being written (in one reproduction, a delete cycle loggeddoc_del_count=0while the daemon emitted dozens ofDELETE DATABASElines — the deletes were attempted but never persisted).Why
ServiceFileHandlerBase.deleteFileFromDB()callsinfoToStub()first, which stats storage to build the stub. In thedelete-dbbranch the file is by definition already gone from storage, so the stub is alwaysnulland the method returnsfalsewithout touching the database. Worse,offlineScannerignored that return value and cleared the file's last-seen entry from the status map regardless. On the following scan the document looks database-only with no last-seen record, is classified asupdate-storage, and the deleted file is written back from the database.Fix (two small, independent parts)
deleteFileFromDB: when the storage stub isnull, fall back to a path-based database delete — fetch the entry by path and, if it exists and is not already deleted, delete it by path. This mirrors what the CLIrmcommand already does. It still returnsfalsewhen there is genuinely nothing to delete.offlineScanner: only clear thefileStatusMaplast-seen record when the database delete actually succeeded. If it returnsfalse, keep the record and log a notice, so a no-op delete can never feed the resurrection path.Net effect: a real tombstone reaches the database and the file stays deleted across scans.
Tests
npm run tsc-checkandnpm run test:unitpass (one unrelated, pre-existingbgWorkerenv failure aside).delete-dbfalse-return path: the last-seen record is retained, a notice is logged, and the file is not resurrected.