{"record":{"id":"7db3bc1afea78e83","repo":"Kareadita/Kavita","slug":"theme-doesnt-exist","errorCode":"theme-doesnt-exist","errorMessage":"theme-doesnt-exist","messagePattern":"theme-doesnt-exist","errorType":"exception","errorClass":"KavitaException","httpStatus":400,"severity":"warning","filePath":"Kavita.Services/SiteThemeService.cs","lineNumber":91,"sourceCode":"        .SetSize(1)\n        .SetAbsoluteExpiration(TimeSpan.FromMinutes(30));\n\n    private const string GithubBaseUrl = \"https://api.github.com\";\n\n    /// <summary>\n    /// Used for refreshing metadata around themes\n    /// </summary>\n    private const string GithubReadme = \"https://raw.githubusercontent.com/Kareadita/Themes/main/README.md\";\n\n    /// <summary>\n    /// Given a themeId, return the content inside that file\n    /// </summary>\n    /// <param name=\"themeId\"></param>\n    /// <param name=\"ct\"></param>\n    /// <returns></returns>\n    public async Task<string> GetContent(int themeId, CancellationToken ct = default)\n    {\n        var theme = await unitOfWork.SiteThemeRepository.GetThemeDto(themeId) ?? throw new KavitaException(\"theme-doesnt-exist\");\n        var themeFile = directoryService.FileSystem.Path.Join(directoryService.SiteThemeDirectory, theme.FileName);\n        if (string.IsNullOrEmpty(themeFile) || !directoryService.FileSystem.File.Exists(themeFile))\n            throw new KavitaException(\"theme-doesnt-exist\");\n\n        return await directoryService.FileSystem.File.ReadAllTextAsync(themeFile, ct);\n    }\n\n    public async Task<List<DownloadableSiteThemeDto>> GetDownloadableThemes(CancellationToken ct = default)\n    {\n        const string cacheKey = \"browse\";\n        // Avoid a duplicate Dark issue some users faced during migration\n        var existingThemes = (await unitOfWork.SiteThemeRepository.GetThemeDtos())\n            .GroupBy(k => k.Name)\n            .ToDictionary(g => g.Key, g => g.First());\n\n        if (cache.TryGetValue(cacheKey, out List<DownloadableSiteThemeDto>? themes) && themes != null)\n        {\n            foreach (var t in themes)","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/SiteThemeService.cs#L73-L109","documentation":"Database-lookup miss in SiteThemeService.GetContent: the repository's GetThemeDto(themeId) returned null, so no theme row matches the requested id. Kavita throws KavitaException('theme-doesnt-exist'); ThemeController maps it to HTTP 400 'Theme file missing or invalid'. The endpoint is anonymous (download-content).","triggerScenarios":"GET /api/theme/download-content?themeId=N where N does not exist in the site_themes table (deleted theme, stale id, id from a different install, typo).","commonSituations":"Frontend holds a themeId after the admin deleted that theme or after a DB reset/restore; user bookmarked a theme CSS URL with an old id; multi-instance confusion where ids differ.","solutions":["GET /api/theme to list valid theme ids and use one of those.","If the theme was deleted, re-download it from /api/theme/browse and use the new id.","Refresh the client theme state after theme add/delete operations so stale ids are never requested."],"exampleFix":"// before\nfetch(`/api/theme/download-content?themeId=${staleId}`)\n\n// after\nconst themes = await fetch('/api/theme').then(r => r.json());\nfetch(`/api/theme/download-content?themeId=${themes[0].id}`)","handlingStrategy":"validation","validationCode":"// Verify the theme id exists before requesting its CSS.\nconst themes: SiteThemeDto[] = await api.get('/api/theme');\nif (!themes.some(t => t.id === themeId)) {\n  showError('Theme not found');\n  return;\n}\nawait api.get(`/api/theme/download-content?themeId=${themeId}`);","typeGuard":"function isKnownThemeId(id: number, themes: { id: number }[]): boolean {\n  return themes.some(t => t.id === id);\n}","tryCatchPattern":null,"preventionTips":["Refresh the theme list after add/delete and only request ids from it.","Do not hard-code theme ids; they are per-install.","Treat a missing-id response as a signal to reload the theme list."],"tags":["theme","validation","not-found","filesystem"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}