JuliusBrussee/caveman · error
: database parent changed
Error message
%w: database parent changed
What it means
inspectSQLiteGeneration (engine/ccr/store_generation.go:46) rejects the database when its parent directory's symlink-resolved path differs from the expected spelling, or when EvalSymlinks reports the parent missing. This is a deliberate security check: it catches the parent directory (or an intermediate component) being swapped for a symlink since the path was canonicalized, which os.SameFile alone cannot detect. Unlike error 1038, this means the environment HAS changed (or the parent vanished) — the store is treated as ErrStorageChanged without the errStorageUnverifiable qualifier.
Solutions
- Check whether ~/.caveman (or the configured CCR parent) is now a symlink — restore it to a real directory or point the store at the canonical target.
- If the parent directory was deleted or moved, recreate it at the original canonical path or reinitialize the store at the new location.
- Always construct the database path via PrepareSQLitePathCanonical; the check compares spellings on purpose and rejects non-canonical paths.
- Treat this as ErrStorageChanged (not unverifiable): if a terminal quarantine decision triggered, follow the store's quarantine/recovery procedure rather than retrying blindly.
- Audit what changed in the environment between open and check (mount changes, container restarts, symlink-swap tools) before recreating the store.
Example fix
// before
// ~/.caveman replaced by symlink to ~/dotfiles/caveman → parent spelling changes
store := ccr.Open("/home/me/.caveman/ccr.db") // error: storage changed: database parent changed
// after
rm ~/.caveman
mkdir -p ~/.caveman # real directory, or exclude it from the dotfile symlink manager
store := ccr.Open("/home/me/.caveman/ccr.db") Defensive patterns
Strategy: try-catch
Validate before calling
func assertCanonicalCCRParent(path string) error {
parent := filepath.Dir(path)
resolved, err := filepath.EvalSymlinks(parent)
if err != nil {
return fmt.Errorf("ccr parent missing or unresolvable: %w", err)
}
if resolved != parent {
return fmt.Errorf("%s contains a symlink component (resolves to %s)", parent, resolved)
}
return nil
}
// run at startup and before every checkGeneration-sensitive operation
Try / catch
files, err := inspectSQLiteGeneration(path)
if err != nil {
if errors.Is(err, ErrStorageChanged) && !errors.Is(err, errStorageUnverifiable) {
// confirmed environment change (parent swapped/removed) — treat the
// store identity as changed and follow the quarantine/recovery path
return fmt.Errorf("ccr storage changed, manual recovery required: %w", err)
}
return err
} Prevention
- Exclude the CCR directory from dotfile managers and symlink-farm tools.
- Always build the database path with PrepareSQLitePathCanonical — non-canonical spellings are rejected by design.
- Recreate the parent at the same canonical path if it was moved; don't repoint via symlink.
- Alert on unexpected ErrStorageChanged without errStorageUnverifiable — it means a real environment swap, not an I/O hiccup.
- Check for parent-directory deletion between open and check (container restarts, TMPDIR changes, volume remounts).
When it happens
Trigger: Calling checkGeneration (via openWithBudgetHooks, secureSQLiteGeneration flows, or the inspection test) when: (a) filepath.EvalSymlinks returns os.ErrNotExist because the parent directory was deleted/moved after the store was opened, or (b) the resolved path string differs from the canonical parent — i.e. some component became a symlink, or a non-canonical path spelling was passed in.
Common situations: A dotfile manager or backup tool replaced ~/.caveman with a symlink; the data directory was moved between inspection passes; /tmp or home redirection changed (TMPDIR, container restarts with different mounts); the store path was constructed by hand with ../ or double slashes instead of via PrepareSQLitePathCanonical.
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
- inspect sqlite parent ACL
- refusing non-regular file
- sqlite parent has no restrictive DACL
- sqlite parent has no security descriptor
- sqlite parent is group/world writable
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/42cd892db3e42e2f.
Report an issue: GitHub.
Appendix: source
Thrown at engine/ccr/store_generation.go:46
var errStorageUnverifiable = errors.New("storage identity could not be verified")
// inspectSQLiteGeneration requires the canonical path PrepareSQLitePathCanonical
// returned. The parent check below is a re-verification that no component became
// a symlink since; it compares spellings on purpose, because following a swapped
// intermediate symlink yields the same directory identity and so cannot be
// detected by os.SameFile. A non-canonical spelling is reported as a change.
func inspectSQLiteGeneration(path string) (sqliteGeneration, error) {
var files sqliteGeneration
if path == ":memory:" {
return files, nil
}
parent := filepath.Dir(path)
resolved, err := filepath.EvalSymlinks(parent)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return files, fmt.Errorf("%w: %w: inspect database parent: %v", ErrStorageChanged, errStorageUnverifiable, err)
}
if err != nil || resolved != parent {
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)View on GitHub (pinned to 3ee70a1026)