{"record":{"id":"08ffdd39aae01a10","repo":"Kareadita/Kavita","slug":"bookmark-dir-permissions","errorCode":"bookmark-dir-permissions","errorMessage":"bookmark-dir-permissions","messagePattern":"bookmark-dir-permissions","errorType":"exception","errorClass":"KavitaException","httpStatus":400,"severity":"error","filePath":"Kavita.Services/SettingsService.cs","lineNumber":421,"sourceCode":"                ((int)updateSettingsDto.PdfRenderResolution).ToString() != setting.Value)\n            {\n                setting.Value = ((int)updateSettingsDto.PdfRenderResolution).ToString();\n                unitOfWork.SettingsRepository.Update(setting);\n            }\n\n            if (setting.Key == ServerSettingKey.HostName && updateSettingsDto.HostName + string.Empty != setting.Value)\n            {\n                setting.Value = (updateSettingsDto.HostName + string.Empty).Trim();\n                setting.Value = UrlHelper.RemoveEndingSlash(setting.Value);\n                unitOfWork.SettingsRepository.Update(setting);\n            }\n\n            if (setting.Key == ServerSettingKey.BookmarkDirectory && bookmarkDirectory != setting.Value)\n            {\n                // Validate new directory can be used\n                if (!await directoryService.CheckWriteAccess(bookmarkDirectory))\n                {\n                    throw new KavitaException(\"bookmark-dir-permissions\");\n                }\n\n                originalBookmarkDirectory = setting.Value;\n\n                // Normalize the path deliminators. Just to look nice in DB, no functionality\n                setting.Value = directoryService.FileSystem.Path.GetFullPath(bookmarkDirectory);\n                unitOfWork.SettingsRepository.Update(setting);\n                updateBookmarks = true;\n\n            }\n\n            if (setting.Key == ServerSettingKey.AllowStatCollection &&\n                updateSettingsDto.AllowStatCollection + string.Empty != setting.Value)\n            {\n                setting.Value = updateSettingsDto.AllowStatCollection + string.Empty;\n                unitOfWork.SettingsRepository.Update(setting);\n            }\n","sourceCodeStart":403,"sourceCodeEnd":439,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/SettingsService.cs#L403-L439","documentation":"Thrown by SettingsService when updating ServerSettingKey.BookmarkDirectory and directoryService.CheckWriteAccess(bookmarkDirectory) returns false. Kavita writes user bookmarks (saved pages) into this directory, so it must be writable by the process; the guard fails fast rather than silently dropping bookmarks later.","triggerScenarios":"Saving server settings with a BookmarkDirectory that the Kavita process cannot write to - missing directory, read-only mount, insufficient filesystem permissions, or an invalid path.","commonSituations":"Docker volume mounted read-only; SELinux/AppArmor denying writes; bookmark path on a network share without write perms; non-existent directory that CheckWriteAccess doesn't auto-create; or path with a typo.","solutions":["Ensure the directory exists and the Kavita process user has write permission (chmod/chown on Linux).","For Docker, mount the bookmark volume read-write (drop ':ro').","Pick a directory inside the existing writable data directory.","Run CheckWriteAccess manually with the Kavita user to confirm permissions before saving."],"exampleFix":"// before\nupdateSettingsDto.BookmarkDirectory = \"/mnt/readonly/bookmarks\";\nawait settingsService.UpdateSettings(updateSettingsDto);\n\n// after\nsudo mkdir -p /var/lib/kavita/bookmarks && sudo chown kavita:kavita /var/lib/kavita/bookmarks\nupdateSettingsDto.BookmarkDirectory = \"/var/lib/kavita/bookmarks\";\nawait settingsService.UpdateSettings(updateSettingsDto);","handlingStrategy":"validation","validationCode":"if (!await directoryService.CheckWriteAccess(bookmarkDirectory))\n    return BadRequest(\"bookmark-dir-permissions\");\nupdateSettingsDto.BookmarkDirectory = bookmarkDirectory;\nawait settingsService.UpdateSettings(updateSettingsDto);","typeGuard":"static async Task<bool> DirectoryIsWritable(string path, IDirectoryService ds) => await ds.CheckWriteAccess(path);","tryCatchPattern":null,"preventionTips":["Ensure the directory exists and is writable by the Kavita process user.","Mount Docker volumes read-write (drop ':ro').","Prefer a path inside the existing writable data directory.","Verify with CheckWriteAccess before saving."],"tags":["settings","filesystem","permissions","configuration","server"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}