{"record":{"id":"7f251b71e90f5bd9","repo":"JuliusBrussee/caveman","slug":"file-changed-while-opening-sqlite-parent-security-windows","errorCode":null,"errorMessage":"file changed while opening","messagePattern":"file changed while opening","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/ccr/sqlite_parent_security_windows.go","lineNumber":105,"sourceCode":"\t\treturn err\n\t}\n\treturn file.Close()\n}\n\nfunc chmodSQLiteFile(path string, info os.FileInfo) error {\n\t// Windows locks are handle-based, so closing this separate descriptor does\n\t// not release the locks held by SQLite.\n\tfile, err := os.OpenFile(path, os.O_RDWR, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\topened, err := file.Stat()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !os.SameFile(info, opened) {\n\t\treturn fmt.Errorf(\"file changed while opening\")\n\t}\n\treturn file.Chmod(0o600)\n}\n","sourceCodeStart":87,"sourceCodeEnd":109,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/engine/ccr/sqlite_parent_security_windows.go#L87-L109","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: two processes racing on the same store\nengine.New(..., StorePath: sharedPath) // in process A and B concurrently\n// after: serialize or isolate the store path\n// process A\nmu.Lock(); defer mu.Unlock(); engine.New(..., StorePath: sharedPath)\n// or give each process its own store\nengine.New(..., StorePath: perProcessPath)","handlingStrategy":"retry","validationCode":"// Before initializing the store, verify the db path is stable and not in a synced folder\ninfo1, err := os.Stat(ccrPath)\nif err == nil {\n  time.Sleep(50 * time.Millisecond)\n  info2, err2 := os.Stat(ccrPath)\n  if err2 != nil || !os.SameFile(info1, info2) {\n    return fmt.Errorf(\"ccr db path %s is being replaced concurrently\", ccrPath)\n  }\n}","typeGuard":null,"tryCatchPattern":"for attempt := 0; attempt < 3; attempt++ {\n  err := store.Put(ctx, obj)\n  if err != nil && strings.Contains(err.Error(), \"file changed while opening\") {\n    time.Sleep(100 * time.Millisecond << attempt) // backoff and retry\n    continue\n  }\n  return err\n}\nreturn fmt.Errorf(\"ccr db keeps changing during open; check for concurrent writers or sync clients\")","preventionTips":["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"],"tags":["filesystem","concurrency","windows","taint-race"],"backgroundTag":"conflicting-file-access","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"}