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
- GET /api/streams/sidenav before offering the action, and disable/hide 'Add' when any returned stream already has externalSourceId === N.
- Debounce or disable the add button until the HTTP response resolves to prevent duplicate submissions.
- 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
- Cache the current side-nav list and treat any source already present as non-addable.
- Disable the Add button immediately on click and re-enable only after the response.
- Refresh GET /api/streams/sidenav whenever the add flow is opened to avoid stale state.
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
- external-source-doesnt-exist
- external-source-already-exists
- collection-tag-duplicate
- name-already-in-use
- errors.theme-already-in-use
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/072c5b8e1e0bb1e7.
Report an issue: GitHub.