Kareadita/Kavita · warning · KavitaException

external-source-doesnt-exist

Error message

external-source-doesnt-exist

What it means

Thrown by StreamService.CreateSideNavStreamFromExternalSource when AppUserExternalSourceRepository.GetById(externalSourceId) returns null — the external source does not exist (or does not belong to the user). The message is translated to 'External Source doesn't exist'. StreamController.AddSideNavFromExternalSource does not catch KavitaException, so it surfaces as HTTP 500 via ExceptionMiddleware.

Source

Thrown at Kavita.Services/StreamService.cs:198

            Order = createdStream.Order,
            SmartFilterEncoded = smartFilter.Filter,
            StreamType = createdStream.StreamType
        };


        await eventHub.SendMessageToAsync(MessageFactory.SideNavUpdate, MessageFactory.SideNavUpdateEvent(userId),
            userId, ct);
        return ret;
    }

    public async Task<SideNavStreamDto> CreateSideNavStreamFromExternalSource(int userId, int externalSourceId,
        CancellationToken ct = default)
    {
        var user = await unitOfWork.UserRepository.GetUserByIdAsync(userId, AppUserIncludes.SideNavStreams, ct);
        if (user == null) throw new KavitaException(await localizationService.TranslateAsync(userId, "no-user"));

        var externalSource = await unitOfWork.AppUserExternalSourceRepository.GetById(externalSourceId, ct);
        if (externalSource == null) throw new KavitaException(await localizationService.TranslateAsync(userId, "external-source-doesnt-exist"));

        var stream = user?.SideNavStreams.FirstOrDefault(d => d.ExternalSourceId == externalSourceId);
        if (stream != null) throw new KavitaException(await localizationService.TranslateAsync(userId, "external-source-already-in-use"));

        var maxOrder = user!.SideNavStreams.Max(d => d.Order);
        var createdStream = new AppUserSideNavStream()
        {
            Name = externalSource.Name,
            IsProvided = false,
            StreamType = SideNavStreamType.ExternalSource,
            Visible = true,
            Order = maxOrder + 1,
            ExternalSourceId = externalSource.Id
        };

        user.SideNavStreams.Add(createdStream);
        unitOfWork.UserRepository.Update(user);
        await unitOfWork.CommitAsync(ct);

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Use an externalSourceId from the user's current external sources (GET /api/stream/external-sources).
  2. Refresh the external sources list before offering 'add to side nav'.
  3. Recreate the external source if it was deleted, then add the side-nav stream.
Defensive patterns

Strategy: validation

Validate before calling

const sources = await api.get('/api/stream/external-sources');
if (!sources.some(s => s.id === externalSourceId)) {
  showError('External Source not found');
  return;
}
await api.post(`/api/stream/add-sidenav-stream-from-external-source?externalSourceId=${externalSourceId}`);

Type guard

function isKnownExternalSourceId(id: number, sources: { id: number }[]): boolean {
  return sources.some(s => s.id === id);
}

Prevention

When it happens

Trigger: POST /api/stream/add-sidenav-stream-from-external-source?externalSourceId=N with a deleted, invalid, or other-user external source id.

Common situations: Stale externalSourceId cached in the UI after the source was deleted; deep link with an old id; cross-user id confusion.

Related errors


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