JuliusBrussee/caveman · critical

: inspect database

Error message

%w: inspect database%s: %v

What it means

The main database file (path + "" suffix) disappeared while its identity was being inspected. Because the main DB file must always exist for a valid store, ErrNotExist on it is treated as proof the storage changed — ErrStorageChanged is returned. In checkGeneration this error latches terminal quarantine: the Store is unusable for the rest of the process.

Solutions

  1. Restore the database file at the original path (from backup) or let a fresh process recreate it: stop the process, then reopen.
  2. Do not try to reuse the current Store — quarantine is latched; instantiate a new Store via Open/OpenWithBudget after the file situation is fixed.
  3. Exclude the store directory from cleanup tools (bleachbit, tmpwatch, cron rm jobs).
  4. If the database was deliberately moved, update the store path configuration and restart.
  5. Check for concurrent processes deleting/racing on the same ccr.db path and serialize them.

Example fix

// before
store.Put(rec) // ccr.db deleted underneath -> quarantined
// after
if _, err := os.Stat(dbPath); err != nil {
    store.Close()
    store, _ = ccr.Open(dbPath)
}
store.Put(rec)
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := os.Stat(dbPath); err != nil {
    // db missing: reopen fresh before calling the store
    store.Close(); store, _ = ccr.Open(dbPath)
}

Try / catch

if errors.Is(err, ccr.ErrStorageChanged) {
    // quarantine is terminal: replace the Store
    store, _ = ccr.Open(dbPath) // new process-level store
}

Prevention

When it happens

Trigger: The .db file itself (not -wal/-shm, which may legitimately be absent) is missing when a Store method runs or when OpenWithBudget inspects before opening — deleted by a cleanup tool, replaced by a rename, or removed while the connection stayed open.

Common situations: Disk cleanup deletes ~/.caveman/ccr.db while the agent runs; a deployment script recreates the database file; the file was moved to another volume; the process was resumed from a snapshot where the file no longer exists.

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 JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/f32a06980980d9fd. Report an issue: GitHub.

Appendix: source

Thrown at engine/ccr/store_generation.go:64

		return files, fmt.Errorf("%w: database parent changed", ErrStorageChanged)
	}
	info, err := os.Stat(parent)
	if errors.Is(err, os.ErrNotExist) {
		return files, fmt.Errorf("%w: database parent changed", ErrStorageChanged)
	}
	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)
}

View on GitHub (pinned to 3ee70a1026)