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

  1. Run a Force Scan on the series/library so Kavita regenerates KOReader hashes for each MangaFile.
  2. Verify the file KOReader has open is byte-identical (same path/edition) to the one in Kavita's library.
  3. If the book was removed intentionally, ignore the error; otherwise re-add the missing file and rescan.
  4. 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

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.