JuliusBrussee/caveman · error
file changed while opening
Error message
file changed while opening
What it means
chmodSQLiteFile opens the SQLite database file to tighten its mode to 0600 and, before doing so, verifies that the file it just opened is the same file that was stat'ed earlier (os.SameFile on device/inode identity). If they differ, the file was replaced, renamed, or recreated between the initial check and the open, so the chmod is aborted to avoid changing permissions on the wrong file. It is a TOCTOU (time-of-check-to-time-of-use) guard on the CCR store's Windows path.
Solutions
- Retry the operation; if the file was transiently replaced, a second attempt typically sees a stable file and passes
- Ensure only one engine instance uses the same CCR database path at a time (stop other processes/agents sharing the profile)
- Exclude the CCR directory from file-sync clients (OneDrive/Dropbox) and antivirus real-time replacement so the db file is not swapped during open
- If the file was intentionally recreated, re-run initialization against the fresh file rather than reusing the stale FileInfo
Example fix
// before: two processes racing on the same store engine.New(..., StorePath: sharedPath) // in process A and B concurrently // after: serialize or isolate the store path // process A mu.Lock(); defer mu.Unlock(); engine.New(..., StorePath: sharedPath) // or give each process its own store engine.New(..., StorePath: perProcessPath)
Defensive patterns
Strategy: retry
Validate before calling
// Before initializing the store, verify the db path is stable and not in a synced folder
info1, err := os.Stat(ccrPath)
if err == nil {
time.Sleep(50 * time.Millisecond)
info2, err2 := os.Stat(ccrPath)
if err2 != nil || !os.SameFile(info1, info2) {
return fmt.Errorf("ccr db path %s is being replaced concurrently", ccrPath)
}
} Try / catch
for attempt := 0; attempt < 3; attempt++ {
err := store.Put(ctx, obj)
if err != nil && strings.Contains(err.Error(), "file changed while opening") {
time.Sleep(100 * time.Millisecond << attempt) // backoff and retry
continue
}
return err
}
return fmt.Errorf("ccr db keeps changing during open; check for concurrent writers or sync clients") Prevention
- Run only one engine instance per CCR database path
- Exclude the CCR directory from OneDrive/Dropbox/sync tools and quarantine-style antivirus
- Avoid external scripts that delete/recreate the db file while the engine runs
- If the file must be replaced, stop the engine first, replace, then restart
When it happens
Trigger: chmodSQLiteFile is invoked during store setup and the DB file at the given path is deleted and recreated, replaced by a new file, or swapped (e.g. by a concurrent process running maintenance/restore, a sync client replacing the file, or another engine instance reinitializing the store) between the initial Stat and the open.
Common situations: Two engine processes starting simultaneously against the same ~/.caveman/ccr.db; OneDrive/Dropbox sync tools replacing the db file during startup; a cleanup script or antivirus quarantine/recreate cycle touching the CCR file mid-initialization.
Related errors
- cannot safely launch Windows command shim
- inspect sqlite parent ACL
- sqlite parent grants broad Windows write access
- sqlite parent has no restrictive DACL
- sqlite parent has no security descriptor
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/7f251b71e90f5bd9.
Report an issue: GitHub.
Appendix: source
Thrown at engine/ccr/sqlite_parent_security_windows.go:105
return err
}
return file.Close()
}
func chmodSQLiteFile(path string, info os.FileInfo) error {
// Windows locks are handle-based, so closing this separate descriptor does
// not release the locks held by SQLite.
file, err := os.OpenFile(path, os.O_RDWR, 0)
if err != nil {
return err
}
defer file.Close()
opened, err := file.Stat()
if err != nil {
return err
}
if !os.SameFile(info, opened) {
return fmt.Errorf("file changed while opening")
}
return file.Chmod(0o600)
}
View on GitHub (pinned to 3ee70a1026)