{"record":{"id":"c14fb24535b048fc","repo":"JuliusBrussee/caveman","slug":"file-changed-while-securing-store-sqlite","errorCode":null,"errorMessage":"file changed while securing","messagePattern":"file changed while securing","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/ccr/store_sqlite.go","lineNumber":371,"sourceCode":"\t}\n\terr = chmodSQLiteFile(path, info)\n\tif errors.Is(err, os.ErrNotExist) && !create {\n\t\t// The sidecar vanished while securing it — a concurrent process\n\t\t// checkpointed the WAL and removed it. Nothing left to secure.\n\t\treturn nil\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\tsecured, err := os.Lstat(path)\n\tif errors.Is(err, os.ErrNotExist) && !create {\n\t\treturn nil\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !os.SameFile(info, secured) {\n\t\treturn fmt.Errorf(\"file changed while securing\")\n\t}\n\treturn nil\n}\n\nfunc configureStorageBudget(db *sql.DB, maxBytes int64) error {\n\tvar pageSize int64\n\tif err := db.QueryRow(`PRAGMA page_size`).Scan(&pageSize); err != nil {\n\t\treturn fmt.Errorf(\"read page size: %w\", err)\n\t}\n\tif pageSize <= 0 {\n\t\treturn errors.New(\"invalid sqlite page size\")\n\t}\n\tmaxPages := maxBytes / pageSize\n\tif maxPages < minimumStoragePages {\n\t\treturn fmt.Errorf(\"budget %d is below CCR storage minimum %d\", maxBytes, minimumStoragePages*pageSize)\n\t}\n\tvar applied int64\n\tif err := db.QueryRow(fmt.Sprintf(`PRAGMA max_page_count=%d`, maxPages)).Scan(&applied); err != nil {","sourceCodeStart":353,"sourceCodeEnd":389,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/engine/ccr/store_sqlite.go#L353-L389","documentation":"After chmodding, secureSQLiteFile re-stats the file and requires os.SameFile(info, secured) to hold, i.e. the same inode/device as when it was inspected. This error means the file at the path was replaced (different inode) between the initial Lstat and the post-chmod Stat, so the permission change may have been applied to a different inode than the one that will be used.","triggerScenarios":"Two processes opening/securing the same CCR store concurrently while one recreates the db file; an external script (vacuum, backup-restore, temp-file-rename) replacing the db file during startup; antivirus or sync tools swapping the file mid-operation.","commonSituations":"Multiple engine instances pointed at the same recovery.db started simultaneously; a deploy script restoring a db backup while the engine boots; Dropbox/rsync-style atomic replace (write temp + rename) colliding with startup.","solutions":["Ensure only one process opens the CCR store at a time; serialize startup or use a lock file","Retry the Open once the conflicting writer finishes","Stop sync/backup jobs from touching the store directory while the engine starts","If the error is transient and rare, wrap Open in a short retry loop"],"exampleFix":"// before\nstore, err := ccr.Open(dbPath) // races with backup restore\n// after\nfor i := 0; i < 3; i++ {\n    store, err = ccr.Open(dbPath)\n    if err == nil || !strings.Contains(fmt.Sprint(err), \"file changed while securing\") { break }\n    time.Sleep(200 * time.Millisecond)\n}","handlingStrategy":"retry","validationCode":"// best-effort pre-check: ensure no other process holds the db\nif f, err := os.OpenFile(dbPath, os.O_EXCL|os.O_CREATE, 0o600); err == nil {\n    defer os.Remove(dbPath + \".lock\") // external lock convention\n    f.Close()\n}","typeGuard":null,"tryCatchPattern":"var store *ccr.Store\nvar err error\nfor i := 0; i < 3; i++ {\n    store, err = ccr.Open(dbPath)\n    if err == nil || !strings.Contains(err.Error(), \"file changed while securing\") { break }\n    time.Sleep(250 * time.Millisecond)\n}","preventionTips":["Only one process should open a given CCR store","Pause backup/sync jobs during engine startup","Use atomic-rename writers carefully around the db path"],"tags":["go","sqlite","concurrency","race"],"backgroundTag":"file-changed-while-securing","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}