{"record":{"id":"fa95ab31858e9f69","repo":"JuliusBrussee/caveman","slug":"sqlite-parent-q-must-be-a-directory","errorCode":null,"errorMessage":"sqlite parent %q must be a directory","messagePattern":"sqlite parent %q must be a directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/ccr/store_sqlite.go","lineNumber":301,"sourceCode":"\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\treturn \"\", fmt.Errorf(\"ccr sqlite path is required\")\n\t}\n\tparent := filepath.Dir(path)\n\tabsoluteParent, err := filepath.Abs(parent)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"resolve sqlite parent %q: %w\", parent, err)\n\t}\n\tresolvedParent, err := filepath.EvalSymlinks(absoluteParent)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"resolve sqlite parent %q: %w\", parent, err)\n\t}\n\tparentInfo, err := os.Stat(resolvedParent)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"inspect sqlite parent %q: %w\", resolvedParent, err)\n\t}\n\tif !parentInfo.IsDir() {\n\t\treturn \"\", fmt.Errorf(\"sqlite parent %q must be a directory\", resolvedParent)\n\t}\n\tif err := validateSQLiteParentSecurity(resolvedParent, parentInfo); err != nil {\n\t\treturn \"\", err\n\t}\n\tcanonicalPath := filepath.Join(resolvedParent, filepath.Base(path))\n\tif err := secureSQLiteFile(canonicalPath, true); err != nil {\n\t\treturn \"\", fmt.Errorf(\"secure sqlite %q: %w\", canonicalPath, err)\n\t}\n\tfor _, suffix := range []string{\"-wal\", \"-shm\"} {\n\t\tif err := secureSQLiteFile(canonicalPath+suffix, false); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"secure sqlite sidecar %q: %w\", canonicalPath+suffix, err)\n\t\t}\n\t}\n\treturn canonicalPath, nil\n}\n\nfunc secureSQLiteFiles(path string) error {\n\tif path == \":memory:\" {","sourceCodeStart":283,"sourceCodeEnd":319,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/engine/ccr/store_sqlite.go#L283-L319","documentation":"After resolving symlinks, PrepareSQLitePathCanonical stats the resolved parent and requires it to be a directory. This error is thrown when the resolved parent path exists but is a regular file, socket, fifo, or other non-directory entry, so a SQLite database cannot live inside it.","triggerScenarios":"Calling Open/PrepareSQLitePath with a path like /path/to/file.db/subdir/db.sqlite where an intermediate component is a file; pointing the db path at a location whose 'parent' is actually a file (e.g. configured path 'data.db/db.sqlite'); a mountpoint replaced a directory with a regular file.","commonSituations":"Config where the user set the db 'directory' option to the file path instead of its folder; a tar/backup restore replaced a directory with a file; container image layering where a file shadows an expected directory.","solutions":["Check the resolved parent with `file` / `os.Stat` and confirm what it actually is","Correct the configured path so the parent component is a real directory","Rename/remove the offending file at that path and recreate the directory with os.MkdirAll","If the path was produced by string concatenation, fix the join so the db filename is not nested under a file path"],"exampleFix":"// before\nccr.Open(\"/data/ccr.db/store.db\") // /data/ccr.db is a file\n// after\nccr.Open(\"/data/ccr/store.db\") // parent /data/ccr is a directory","handlingStrategy":"validation","validationCode":"func parentIsDir(dbPath string) error {\n    fi, err := os.Stat(filepath.Dir(dbPath))\n    if err != nil { return err }\n    if !fi.IsDir() { return fmt.Errorf(\"%s is not a directory\", filepath.Dir(dbPath)) }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := ccr.Open(dbPath); err != nil && strings.Contains(err.Error(), \"must be a directory\") {\n    // fix config or recreate the parent as a directory before retrying\n    return fmt.Errorf(\"bad ccr path config: %w\", err)\n}","preventionTips":["Configure the db directory, never a file path, as the parent","Stat the parent once at config-load time","Guard against backup/restore jobs replacing directories with files"],"tags":["go","sqlite","filesystem"],"backgroundTag":"path-is-not-a-directory","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"}