JuliusBrussee/caveman · critical

: refusing non-regular database

Error message

%w: refusing non-regular database%s

What it means

A database file (main, -wal, or -shm) exists at inspection time but is not a regular file — e.g. it is a symlink, directory, FIFO, or device node. SQLite safety guarantees assume regular files, so inspectSQLiteGeneration refuses with ErrStorageChanged. This is a hard identity/security failure; in checkGeneration it latches terminal quarantine for the Store.

Solutions

  1. Replace the non-regular object with a real file: delete the symlink/directory/FIFO and let a fresh Store create the database (or restore from backup).
  2. Restart the process with a new Store — quarantine is latched and the old connection is intentionally never reused.
  3. Configure your dotfile/sync manager to symlink the whole ~/.caveman directory (whose parent is checked) rather than individual database files, or use mounts instead of per-file symlinks.
  4. Audit for path collisions: ensure no script creates ccr.db, ccr.db-wal, or ccr.db-shm as directories or special files.
  5. Keep the store in a user-owned directory that external tools do not manage.

Example fix

# before
ln -s /sync/ccr.db ~/.caveman/ccr.db
# after
mv /sync/ccr.db ~/.caveman/ccr.db   # real file in place of symlink
Defensive patterns

Strategy: validation

Validate before calling

for _, sfx := range []{"", "-wal", "-shm"} {
    if fi, err := os.Lstat(dbPath + sfx); err == nil && !fi.Mode().IsRegular() {
        return fmt.Errorf("%s is not a regular file", dbPath+sfx)
    }
}

Prevention

When it happens

Trigger: path, path+"-wal", or path+"-shm" was replaced by a non-regular filesystem object while the store was open, or such an object was present at open time: someone symlinked ccr.db to another location, created a directory named ccr.db-wal, or placed a named pipe where a file belongs.

Common situations: Dotfile managers symlink ~/.caveman/ccr.db into a synced folder; a build/test script mkdir's a path colliding with the WAL sidecar; an attacker or accident swaps in a symlink to redirect writes.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/828d268580520ad7. Report an issue: GitHub.

Appendix: source

Thrown at engine/ccr/store_generation.go:70

	if err != nil {
		return files, fmt.Errorf("%w: %w: inspect database parent: %v", ErrStorageChanged, errStorageUnverifiable, err)
	}
	if err := validateSQLiteParentSecurity(parent, info); err != nil {
		return files, fmt.Errorf("%w: %w: %v", ErrStorageChanged, errStorageUnverifiable, err)
	}
	for i, suffix := range sqliteSuffixes {
		info, err := inspectSQLiteFile(path + suffix)
		if errors.Is(err, os.ErrNotExist) && i != 0 {
			continue
		}
		if errors.Is(err, os.ErrNotExist) {
			return files, fmt.Errorf("%w: inspect database%s: %v", ErrStorageChanged, suffix, err)
		}
		if err != nil {
			return files, fmt.Errorf("%w: %w: inspect database%s: %v", ErrStorageChanged, errStorageUnverifiable, suffix, err)
		}
		if !info.Mode().IsRegular() {
			return files, fmt.Errorf("%w: refusing non-regular database%s", ErrStorageChanged, suffix)
		}
		files[i] = info
	}
	return files, nil
}

func sameSQLiteFile(a, b os.FileInfo) bool {
	if a == nil || b == nil {
		return a == nil && b == nil
	}
	return os.SameFile(a, b)
}

// sameExisting also permits sidecars to be created while opening a clean DB.
func (a sqliteGeneration) sameExisting(b sqliteGeneration) bool {
	for i := range a {
		if a[i] != nil && !sameSQLiteFile(a[i], b[i]) {
			return false

View on GitHub (pinned to 3ee70a1026)