benbjohnson/litestream · error
cannot delete vfs file
Error message
cannot delete vfs file
What it means
The VFS Delete method (xDelete) was asked to remove a file. When deleteTempFile reports that the name is not a tracked temp file (errTempFileNotFound), the VFS cannot delete it and returns this opaque 'cannot delete vfs file' error. It is intentionally returned instead of success because the file is neither a real file nor a temp file the VFS knows about, so honoring the delete is impossible.
Source
Thrown at vfs.go:314
cfg := GetVFSConfig(name)
uriCfg, err := ParseVFSURIConfig(uriParameters)
if err != nil {
return nil, err
}
return MergeVFSConfig(cfg, uriCfg), nil
}
func (vfs *VFS) Delete(name string, dirSync bool) error {
slog.Debug("deleting file", "name", name, "dirSync", dirSync)
err := vfs.deleteTempFile(name)
if err == nil {
return nil
}
if errors.Is(err, os.ErrNotExist) {
return nil
}
if errors.Is(err, errTempFileNotFound) {
return fmt.Errorf("cannot delete vfs file")
}
return err
}
func (vfs *VFS) Access(name string, flag sqlite3vfs.AccessFlag) (bool, error) {
slog.Debug("accessing file", "name", name, "flag", flag)
if strings.HasSuffix(name, "-wal") {
return vfs.accessWAL(name, flag)
}
if vfs.isTempFileName(name) {
return vfs.accessTempFile(name, flag)
}
return false, nil
}
func (vfs *VFS) accessWAL(name string, flag sqlite3vfs.AccessFlag) (bool, error) {
return false, nilView on GitHub (pinned to 4ed7a308f6)
Solutions
- Only call Delete (via SQLite) for files that were previously opened through the same VFS instance and are still tracked.
- Check that the file name is a temp-file style name matching what openTempFile produced (base + '-' + fnv64 hash); if you delete manually, delete the actual temp path.
- If the registry was lost (restart), drop the file directly on disk with os.Remove instead of going through the VFS.
- Upgrade/verify VFS version: newer versions may treat untracked-but-nonexistent deletes as no-ops.
Example fix
// before
db.Exec("DELETE FROM sqlite_master") // triggers xDelete on untracked journal names
// after
// ensure the VFS instance that opened the file is the same one handling deletes,
// or remove the underlying temp file directly:
os.Remove(tempPathFor(name)) Defensive patterns
Strategy: try-catch
Validate before calling
// before deleting, confirm the VFS actually tracks the file
if !vfs.IsTempFileName(name) && !fileExists(name) { skipDelete() } Try / catch
if err := sqlite delete...; err != nil && strings.Contains(err.Error(), "cannot delete vfs file") {
// file untracked; optionally os.Remove the underlying temp path
} Prevention
- Keep a single long-lived VFS instance for the process lifetime.
- Avoid manual deletes of SQLite journal/temp names.
- Don't rely on Delete for files already removed via DeleteOnClose.
- Log file names with errors to spot untracked-name bugs early.
When it happens
Trigger: SQLite calls xDelete for a name that is not registered in the VFS temp-file map and does not exist on disk — e.g. deleting a main-database alias, a journal whose temp file was already dropped from the registry, or an arbitrary path passed to xDelete after a process restart lost the in-memory tracking.
Common situations: Application deletes a temp/journal file after the VFS registry was reset or the connection pool cycled; TestVFS_TempFileDeleteOnClose-style flows where DeleteOnClose already removed the file and un-tracked it before SQLite issues xDelete; calling Delete on a name that was never opened through this VFS instance.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- temp file not tracked
- could not parse time: %s
- unsupported file control op: %d
- snapshot wal read exceeded bound: max offset %d > end offset
- write snapshot ltx: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/fcd386da4525a333.
Report an issue: GitHub.