Kareadita/Kavita · error · KavitaException

annotation-export-failed

Error message

annotation-export-failed

What it means

Catch-all thrown by AnnotationService.ExportAnnotations (line 241) for any exception during the export pipeline — querying annotations, resolving user highlight-slot preferences, building the deep-link hostname, grouping/serialization, or a KeyNotFoundException on users[annotation.UserId]. The original exception is logged with the UserId; the client receives the opaque 'annotation-export-failed' code. Triggered via POST /api/annotation/export or /api/annotation/export-filter.

Source

Thrown at Kavita.Services/AnnotationService.cs:241

                                    obsidianTags = new[] { "#kavita", $"#{seriesGroup.Key.SeriesName.ToLowerInvariant().Replace(" ", "-")}", "#highlights" },
                                    obsidianTitle,
                                    obsidianBacklinks = new[] { $"[[{seriesGroup.Key.SeriesName} Series]]", $"[[{volumeGroup.Key.VolumeName}]]" }
                                };
                            }).ToArray(),
                        }).ToArray(),
                }).ToArray();

            // Serialize to JSON
            var json = JsonSerializer.Serialize(exportData, ExportJsonSerializerOptions);

            logger.LogInformation("Successfully exported {AnnotationCount} annotations for user {UserId}", annotations.Count, userId);

            return json;
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Failed to export annotations for user {UserId}", userId);
            throw new KavitaException("annotation-export-failed");
        }
    }

    private string StripHtml(string? html)
    {
        if (string.IsNullOrEmpty(html))
        {
            return string.Empty;
        }

        try
        {
            var document = new HtmlDocument();
            document.LoadHtml(html);

            return document.DocumentNode.InnerText.Replace(" ", " ");
        }
        catch (Exception exception)

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Inspect the logged 'ex' for the exact failure (KeyNotFoundException is common).
  2. Use users.GetValueOrDefault(annotation.UserId) instead of the indexer to tolerate a missing user.
  3. If the export is very large, stream the JSON or paginate instead of materializing one big anonymous array.
  4. Ensure all annotation owners still exist as users with UserPreferences.

Example fix

// before — throws KeyNotFoundException if a user is missing
var user = users[annotation.UserId];

// after — tolerate a missing user
if (!users.TryGetValue(annotation.UserId, out var user)) continue;
Defensive patterns

Strategy: try-catch

Try / catch

catch (KavitaException ex) { return BadRequest(await localizationService.TranslateAsync(UserId, ex.Message)); }
// In the service, use a tolerant lookup to avoid KeyNotFoundException:
if (!users.TryGetValue(annotation.UserId, out var user)) continue;

Prevention

When it happens

Trigger: POST /api/annotation/export (or export-filter) that fails: a user referenced by an annotation has no UserPreferences (users[annotation.UserId] throws KeyNotFoundException), settings.HostName is malformed, JSON serialization of the anonymous projection fails, or a DB read error occurs.

Common situations: An annotation whose AppUser was deleted but whose row remains (stale foreign key) → users dictionary lookup throws; very large export exhausting memory during JsonSerializer.Serialize; missing ServerSettings row.

Related errors


AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13). Data as JSON: /api/errors/83d8d37ff200eba5. Report an issue: GitHub.