benbjohnson/litestream · error

vfs file not found: id=

Error message

vfs file not found: id=%d

What it means

RegisterVFSConnection maps a SQLite connection pointer (dbPtr) to a VFS file ID. Before storing the mapping it checks that the file ID exists via lookupVFSFile; if not, it refuses to register. This guards against registering connections for files that were already closed or never opened through the VFS.

Solutions

  1. Open the database through the VFS first and use the file ID returned from that open
  2. Check for double-close races: ensure the file is not unregistered before connection registration
  3. Log/verify the fileID matches the one returned by your latest VFS open call

Example fix

// before: stale id after reopen
fileID := cachedID
litestreamvfs.RegisterVFSConnection(dbPtr, fileID) // vfs file not found
// after: use id from the current open
file, fileID := litestreamvfs.Open(dbPath)
_ = litestreamvfs.RegisterVFSConnection(dbPtr, fileID)
Defensive patterns

Strategy: validation

Validate before calling

func ensureFileRegistered(fileID uint64) error {
    if _, ok := lookupVFSFile(fileID); !ok {
        return fmt.Errorf("cannot register connection: vfs file %d not open", fileID)
    }
    return nil
}

Type guard

func hasVFSFile(fileID uint64) bool { _, ok := lookupVFSFile(fileID); return ok }

Try / catch

if err := litestreamvfs.RegisterVFSConnection(dbPtr, fileID); err != nil {
    if strings.Contains(err.Error(), "vfs file not found") {
        return reopenAndRegister(ctx)
    }
    return err
}

Prevention

When it happens

Trigger: Calling litestream_vfs_register_connection (or the Go API) with a fileID that was never created by the VFS open, or whose VFSFile was already unregistered/closed.

Common situations: Application-level cache of stale file IDs after reopening the database; concurrent close of the VFS file while another goroutine registers its connection; passing the wrong ID after multiple database opens.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at vfs.go:2859

			return infos, nil
		}
		if !errors.Is(err, ErrTxNotAvailable) {
			return nil, fmt.Errorf("cannot calc restore plan: %w", err)
		}

		f.logger.Debug("no backup files available yet, waiting", "interval", f.PollInterval)
		select {
		case <-time.After(f.PollInterval):
		case <-f.ctx.Done():
			return nil, fmt.Errorf("no backup files available: %w", f.ctx.Err())
		}
	}
}

// RegisterVFSConnection maps a SQLite connection handle to its VFS file ID.
func RegisterVFSConnection(dbPtr uintptr, fileID uint64) error {
	if _, ok := lookupVFSFile(fileID); !ok {
		return fmt.Errorf("vfs file not found: id=%d", fileID)
	}
	vfsConnectionMap.Store(dbPtr, fileID)
	return nil
}

// UnregisterVFSConnection removes a connection mapping.
func UnregisterVFSConnection(dbPtr uintptr) {
	vfsConnectionMap.Delete(dbPtr)
}

// SetVFSConnectionTime rebuilds the VFS index for a connection at a timestamp.
func SetVFSConnectionTime(dbPtr uintptr, timestamp string) error {
	file, err := vfsFileForConnection(dbPtr)
	if err != nil {
		return err
	}

	t, err := parseTimeValue(timestamp)

View on GitHub (pinned to 4ed7a308f6)