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, nil

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Only call Delete (via SQLite) for files that were previously opened through the same VFS instance and are still tracked.
  2. 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.
  3. If the registry was lost (restart), drop the file directly on disk with os.Remove instead of going through the VFS.
  4. 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

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


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/fcd386da4525a333. Report an issue: GitHub.