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
- Inspect the logged 'ex' for the exact failure (KeyNotFoundException is common).
- Use users.GetValueOrDefault(annotation.UserId) instead of the indexer to tolerate a missing user.
- If the export is very large, stream the JSON or paginate instead of materializing one big anonymous array.
- 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
- Keep annotation owners and their UserPreferences rows consistent (cascade delete or cleanup orphan annotations).
- For large libraries, stream/paginate the export instead of building one big in-memory JSON.
- Correlate the export 400 with the logged exception (UserId) to find the failing step.
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.