Kareadita/Kavita · error · KavitaException
file-missing
Error message
file-missing
What it means
Thrown inside KoreaderService.SaveProgress when GetByKoreaderHash returns null — no MangaFile row matches the document hash KOReader sent. KOReader identifies documents by a hash; if Kavita has never computed/that hash is stale, the progress event cannot be attached to a chapter. The localization key 'file-missing' is translated per user before throwing.
Source
Thrown at Kavita.Services/KoreaderService.cs:38
ILocalizationService localizationService,
ILogger<KoreaderService> logger)
: IKoreaderService
{
/// <summary>
/// Given a Koreader hash, locate the underlying file and generate/update a progress event.
/// </summary>
/// <param name="koreaderBookDto"></param>
/// <param name="userId"></param>
/// <param name="ct"></param>
public async Task SaveProgress(KoreaderBookDto koreaderBookDto, int userId, CancellationToken ct = default)
{
logger.LogDebug("Saving KOReader progress for User ({UserId}): {KoreaderProgress} - {KoreaderHash}",
userId, koreaderBookDto.progress.Sanitize(), koreaderBookDto.document.Sanitize());
var file = await unitOfWork.MangaFileRepository.GetByKoreaderHash(koreaderBookDto.document, ct);
if (file == null)
{
logger.LogWarning("KOReader progress for unknown book: {BookHash}. Run a force scan on the series to generate KOReader hashes", koreaderBookDto.document.Sanitize());
throw new KavitaException(await localizationService.TranslateAsync(userId, "file-missing"));
}
var userProgressDto = await unitOfWork.AppUserProgressRepository.GetUserProgressDtoAsync(file.ChapterId, userId, ct);
if (userProgressDto == null)
{
var chapterDto = await unitOfWork.ChapterRepository.GetChapterDtoAsync(file.ChapterId, userId, ct);
if (chapterDto == null) throw new KavitaException(await localizationService.TranslateAsync(userId, "chapter-doesnt-exist"));
var volumeDto = await unitOfWork.VolumeRepository.GetVolumeByIdAsync(chapterDto.VolumeId, ct: ct);
if (volumeDto == null) throw new KavitaException(await localizationService.TranslateAsync(userId, "volume-doesnt-exist"));
var seriesDto = await unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(volumeDto.SeriesId, userId, ct);
if (seriesDto == null) throw new KavitaException(await localizationService.TranslateAsync(userId, "series-doesnt-exist"));
userProgressDto = new ProgressDto()
{
PageNum = 0, // This is updated in KoreaderHelper.UpdateProgressDto
ChapterId = file.ChapterId,View on GitHub (pinned to 9c3e540000)
Solutions
- Run a Force Scan on the series/library so Kavita regenerates KOReader hashes for each MangaFile.
- Verify the file KOReader has open is byte-identical (same path/edition) to the one in Kavita's library.
- If the book was removed intentionally, ignore the error; otherwise re-add the missing file and rescan.
- Check logs: the preceding LogWarning includes the BookHash that failed — search the manga_files table for that hash to confirm absence.
Defensive patterns
Strategy: try-catch
Validate before calling
var file = await unitOfWork.MangaFileRepository.GetByKoreaderHash(dto.document, ct);
if (file == null)
return NotFound(new { error = "file-missing", hint = "Run a force scan on the series." }); Try / catch
try { await koreaderService.SaveProgress(dto, userId, ct); }
catch (KavitaException ex) when (ex.Message.Contains("file-missing"))
{ /* return 404 + 'force-scan required' to the device; do not retry */ } Prevention
- Run a Force Scan after adding new books so KOReader hashes are generated.
- Keep the file KOReader reads byte-identical to the one in Kavita's library.
- Surface the failed BookHash (from logs) so users can locate the stale file.
When it happens
Trigger: A KOReader sync POST (SaveProgress) carries a document hash that is absent from the manga_files koreader_hash column. Happens when the file was added but the KOReader hash generation hasn't run, the file was renamed/moved so its hash differs, or the book was deleted from Kavita but the user still has it on the device.
Common situations: Library scanned normally but the KOReader-hash job (force scan) was never invoked; user points KOReader at a copy of the epub that differs in bytes from the one Kavita indexed; hash column migration did not backfill on upgrade.
Related errors
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/9d607472b14fe18f.
Report an issue: GitHub.