Kareadita/Kavita · warning · KavitaException

external-source-already-in-use

Error message

external-source-already-in-use

What it means

Thrown by StreamService.CreateSideNavStreamFromExternalSource when the calling user already has an AppUserSideNavStream bound to the same AppUserExternalSource. Kavita allows only one side-nav entry per external source per user, so re-adding the same source is rejected after the user and the source are both validated. It is a localized KavitaException; note the global ExceptionMiddleware routes a plain KavitaException to the generic handler, so the API client actually receives HTTP 500 carrying the user-facing message (not a 400).

Source

Thrown at Kavita.Services/StreamService.cs:201

        };


        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);

        var ret = new SideNavStreamDto()
        {

View on GitHub (pinned to 9c3e540000)

Solutions

  1. GET /api/streams/sidenav before offering the action, and disable/hide 'Add' when any returned stream already has externalSourceId === N.
  2. Debounce or disable the add button until the HTTP response resolves to prevent duplicate submissions.
  3. If the stream appears missing in the UI, refresh the side-nav (GET /api/streams/sidenav) rather than blindly retrying the add.

Example fix

// before
addSourceToSideNav(id: number) {
  return this.http.post(`streams/add-sidenav-stream-from-external-source?externalSourceId=${id}`, {});
}
// after
addSourceToSideNav(id: number) {
  return this.sidenav$.pipe(
    take(1),
    filter(list => !list.some(s => s.externalSourceId === id)),
    switchMap(() =>
      this.http.post(`streams/add-sidenav-stream-from-external-source?externalSourceId=${id}`, {}))
  );
}
Defensive patterns

Strategy: validation

Validate before calling

// before adding an external source to the side-nav
function canAddExternalSourceToSideNav(sidenav: SideNavStreamDto[], externalSourceId: number): boolean {
  return !sidenav.some(s => s.externalSourceId === externalSourceId);
}

Prevention

When it happens

Trigger: POST /api/streams/add-sidenav-stream-from-external-source?externalSourceId=N (StreamController.AddSideNavFromExternalSource) where N already equals the ExternalSourceId of an existing AppUserSideNavStream owned by the user. The throw is on the line `var stream = user?.SideNavStreams.FirstOrDefault(d => d.ExternalSourceId == externalSourceId); if (stream != null) throw ...`.

Common situations: Double-clicking the 'Add to sidebar' action for an external source; the side-nav list in the UI is stale and still shows an enabled Add control; a previous add succeeded but the optimistic UI update was rolled back, so the user clicks again.

Related errors


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