Kareadita/Kavita · warning · KavitaException

sidenav-stream-only-delete-smart-filter

Error message

sidenav-stream-only-delete-smart-filter

What it means

Thrown by StreamService.DeleteSideNavSmartFilterStream when the loaded stream's StreamType is not SideNavStreamType.SmartFilter. Only user-created SmartFilter side-nav entries are deletable through this endpoint; system streams (Libraries, WantToRead, etc.) and external-source streams must be removed through their own flows. It is a localized KavitaException surfaced as HTTP 500.

Source

Thrown at Kavita.Services/StreamService.cs:365

        var streams2 = await unitOfWork.UserRepository.GetSideNavStreamWithExternalSource(externalSourceId, ct);
        unitOfWork.UserRepository.Delete(streams2);

        await unitOfWork.CommitAsync(ct);
    }

    public async Task DeleteSideNavSmartFilterStream(int userId, int sideNavStreamId, CancellationToken ct = default)
    {
        try
        {
            var stream = await unitOfWork.UserRepository.GetSideNavStream(sideNavStreamId, ct);
            if (stream == null) throw new KavitaException("sidenav-stream-doesnt-exist");

            if (stream.AppUserId != userId) throw new KavitaException("sidenav-stream-doesnt-exist");


            if (stream.StreamType != SideNavStreamType.SmartFilter)
            {
                throw new KavitaException("sidenav-stream-only-delete-smart-filter");
            }

            unitOfWork.UserRepository.Delete(stream);

            await unitOfWork.CommitAsync(ct);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "There was an exception deleting SideNav Smart Filter Stream: {FilterId}", sideNavStreamId);
            throw;
        }
    }

    public async Task DeleteDashboardSmartFilterStream(int userId, int dashboardStreamId, CancellationToken ct = default)
    {
        try
        {
            var stream = await unitOfWork.UserRepository.GetDashboardStream(dashboardStreamId, ct);

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Gate the delete UI on streamType === 'smartFilter'; hide delete for system and external-source streams.
  2. For external-source streams, delete via DELETE /api/streams/delete-external-source instead.
  3. If the user wants to hide (not delete) a non-smart-filter stream, use update-sidenav-stream to set visible=false.

Example fix

// before
canDelete(s: SideNavStreamDto) { return true; }
// after
canDelete(s: SideNavStreamDto) { return s.streamType === 'smartFilter'; }
Defensive patterns

Strategy: type-guard

Validate before calling

function isSmartFilterStream(s: SideNavStreamDto): boolean {
  return s?.streamType === 'smartFilter';
}

Type guard

function isDeletableSideNavStream(s: SideNavStreamDto): s is SideNavStreamDto {
  return s?.streamType === 'smartFilter';
}

Try / catch

on 'sidenav-stream-only-delete-smart-filter', hide the delete control for that stream and switch the user to hide (visible=false) or the external-source delete flow

Prevention

When it happens

Trigger: DELETE /api/streams/smart-filter-side-nav-stream?sideNavStreamId=N where the stream exists and is owned by the user but its StreamType is not SmartFilter (e.g., an external-source or built-in stream).

Common situations: The UI shows a delete affordance on a non-smart-filter stream; an external-source side-nav stream is removed via the wrong endpoint (use delete-external-source flow instead); a built-in stream is targeted.

Related errors


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