Skip to content

fix(server): always fetch unread feeds from offset 0 in AI mode#147

Merged
trett merged 1 commit intomainfrom
fix-pagination
Mar 5, 2026
Merged

fix(server): always fetch unread feeds from offset 0 in AI mode#147
trett merged 1 commit intomainfrom
fix-pagination

Conversation

@trett
Copy link
Owner

@trett trett commented Mar 5, 2026

No description provided.

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a bug in the AI summarization feature where subsequent requests for unread feeds could use an incorrect offset, potentially leading to feeds being missed. By ensuring that the system always fetches unread feeds from the beginning (offset 0) in AI mode, it guarantees that all available unread content is processed, as previously summarized feeds are marked as read and no longer appear in subsequent fetches.

Highlights

  • AI Mode Feed Fetching: The logic for fetching unread feeds in AI mode has been updated to always start from offset 0, ensuring that all unread items are processed sequentially without skipping.
  • Remaining Feed Count Calculation: The calculation for totalRemaining unread feeds was adjusted to correctly reflect the new offset behavior.
  • New Test Case: A new test was added to SummarizeServiceSpec to explicitly verify that getUnreadFeeds is invoked with an offset of 0 when streamSummary is called, regardless of the provided offset.
Changelog
  • server/src/main/scala/ru/trett/rss/server/services/SummarizeService.scala
    • Modified the streamSummary method to pass 0 as the offset to feedRepository.getUnreadFeeds when processing feeds for AI summarization.
    • Updated the remainingAfterThis calculation to totalUnread - feeds.size to align with the new offset behavior.
  • server/src/test/scala/ru/trett/rss/server/services/SummarizeServiceSpec.scala
    • Added a new test streamSummary should always fetch feeds from offset 0 regardless of provided offset to validate the corrected behavior of fetching unread feeds.
Activity
  • No specific activity (comments, reviews, progress updates) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request intends to fix how unread feeds are fetched in AI mode. However, it introduces a critical security vulnerability: the summary generation process is susceptible to prompt injection, potentially leading to stored Cross-Site Scripting (XSS) due to unsanitized LLM-generated HTML output. Furthermore, the changes unconditionally apply AI mode behavior, causing a regression for non-AI users by breaking pagination and incorrectly marking feeds as read. It is crucial to sanitize LLM output and restore conditional logic for AI mode, along with adding a test case for non-AI mode in SummarizeServiceSpec.scala.

Comment on lines +117 to +121
.drain ++ summarizeStream(
strippedText,
validatedLanguage.displayName,
selectedModel.modelId
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The summarizeStream function at these lines is vulnerable to prompt injection, which can lead to stored Cross-Site Scripting (XSS) as the LLM-generated HTML output is not sanitized. It is crucial to sanitize this output using a library like Jsoup with a strict allow-list. Additionally, the change in this area unconditionally applies 'AI mode' behavior, causing a regression for non-AI users by breaking pagination and incorrectly marking feeds as read. The conditional logic based on user.settings.isAiMode must be restored.

                                .drain ++ summarizeStream(
                                strippedText,
                                validatedLanguage.displayName,
                                selectedModel.modelId
                            ).map {
                                case ru.trett.rss.models.SummaryEvent.Content(text) =>
                                    ru.trett.rss.models.SummaryEvent.Content(org.jsoup.Jsoup.clean(text, org.jsoup.safety.Safelist.relaxed()))
                                case event => event
                            }

Comment on lines +16 to +40
test("streamSummary should always fetch feeds from offset 0 regardless of provided offset") {
val feedRepository = mock[FeedRepository]
val client = mock[Client[IO]]
val user = User("user-id", "User", "user@example.com", User.Settings())

implicit val loggerFactory: LoggerFactory[IO] = NoOpFactory[IO]

// Mock getTotalUnreadCount
(feedRepository.getTotalUnreadCount)
.expects("user-id")
.returning(IO.pure(60))

// This is the CRITICAL part: even if streamSummary is called with offset 30,
// it MUST call feedRepository.getUnreadFeeds with offset 0 because
// it's in AI mode and feeds from the previous batch were already marked as read.
(feedRepository
.getUnreadFeeds(_: User, _: Int, _: Int))
.expects(user, 30, 0) // batchSize is 30, expected offset is 0
.returning(IO.pure(List.empty))

val service = new SummarizeService(feedRepository, client, "api-key")

// Call with offset 30 (simulating "Load More" click)
service.streamSummary(user, 30).compile.toList.unsafeRunSync()
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This is a good test for the 'AI mode' behavior. To improve test coverage and prevent regressions like the one I mentioned in SummarizeService.scala, it would be beneficial to add another test case for when AI mode is disabled (isAiMode is false).

This test would verify that:

  • The offset parameter passed to streamSummary is correctly passed through to feedRepository.getUnreadFeeds.
  • feedRepository.markFeedAsRead is not called.

This would ensure that the non-AI mode functionality remains correct and prevent future regressions.

@trett trett merged commit 8653332 into main Mar 5, 2026
1 check passed
@trett trett deleted the fix-pagination branch March 5, 2026 18:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant