Fix episodes queued in Up Next being auto-archived by per-podcast episode limit - #5342
Fix episodes queued in Up Next being auto-archived by per-podcast episode limit#5342joashrajin wants to merge 9 commits into
Conversation
…imit The previous filter in checkPodcastForEpisodeLimitBlocking only excluded the currently-playing episode (top of Up Next). Any episode queued deeper in Up Next could be archived when a newer episode arrived and tripped the per-podcast limit during a background refresh, silently removing it from the queue. Extend the filter to skip any episode the user has placed in Up Next, matching the pattern already used in episodeCanBeCleanedUp. Linear: PCDROID-571
Reproduces the PCDROID-571 scenario: episode limit set to 1, two episodes for the same podcast, the older one queued in Up Next behind a newer one. The legacy filter that only excluded `getCurrentEpisode()` would archive the older episode here; with the upNextQueue.contains guard it stays. Linear: PCDROID-571
There was a problem hiding this comment.
Pull request overview
Fixes an auto-archive edge case where episodes queued in Up Next (but not currently playing) could be archived when a per-podcast episode limit is exceeded during background refresh, causing queued episodes to disappear unexpectedly.
Changes:
- Update per-podcast episode-limit auto-archive logic to exclude any episode currently present in the Up Next queue (not just the current episode).
- Add an
androidTestregression test to ensure queued (non-current) Up Next episodes are not archived under the per-podcast limit.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/podcast/EpisodeManagerImpl.kt | Extends the episode-limit exclusion check to cover the full Up Next queue to prevent archiving queued episodes. |
| app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/AutoArchiveTest.kt | Adds a regression test reproducing the queued-episode auto-archive scenario under a per-podcast limit of 1. |
| runBlocking { upNext.playLast(newerEpisode, onAdd = null) } | ||
| runBlocking { upNext.playLast(olderEpisode, onAdd = null) } |
There was a problem hiding this comment.
Good catch, addressed in 549b0c7 — dropped the runBlocking wrappers and call playLast directly from the runTest coroutine.
There was a problem hiding this comment.
Update: had to revert this in 2cd7c78 — the instrumented-tests CI job failed after dropping the wrappers. upNext.playLast does withContext(Dispatchers.Default) internally, and the runTest scope here uses an UnconfinedTestDispatcher only as the ioDispatcher — it doesn't control Dispatchers.Default, so the queue can be observed not-yet-populated when the archive check runs immediately after. Matching the existing testAddingInactiveEpisodeToUpNext pattern with runBlocking keeps the ordering reliable.
`runTest` already provides a coroutine scope, so calling the suspend `upNext.playLast` directly is sufficient. Addresses Copilot review feedback on PR #5342.
This reverts commit 549b0c7. The instrumented-tests CI job started failing after that change. `upNext.playLast` dispatches via `withContext(coroutineContext)` where the coroutineContext is `Dispatchers.Default`, which the test's `runTest` scope (using `UnconfinedTestDispatcher` as ioDispatcher) does not control — so the queue can be observed not-yet-populated when the archive check runs immediately after. Restore the `runBlocking` wrappers to match the existing `testAddingInactiveEpisodeToUpNext` pattern and ensure ordering.
|
Claude finished @joashrajin's task in 1m 41s —— View job PR Review: Fix Up Next episodes being auto-archived by per-podcast limit
Thanks for the fix and the detailed writeup — the root cause analysis is spot on. The bug was real: the old
Correctness ✅
Minor observations (non-blocking)
VerdictThe change is correct, minimal, well-tested, and follows an existing pattern in the same file. The only thing I'd genuinely suggest before merge is the CHANGELOG entry. Nice work. 👍 Note: I reviewed against the current branch HEAD. I wasn't able to run the Gradle build/tests in this environment, so I did not execute |
The instrumented test was failing with an NPE: adding a second episode to Up Next takes the hasQueuedItems branch in insertUpNextEpisodeBlocking, which reads trackingAutoPlaySource/lastAutoPlaySource from the unstubbed Settings mock. The earlier runBlocking back-and-forth was a red herring - the failure was deterministic and dispatcher-independent.
# Conflicts: # CHANGELOG.md
|
Claude finished @joashrajin's task in 47s —— View job Re-review: Fix Up Next episodes being auto-archived by per-podcast limit
I re-reviewed the branch after the latest push ( What's resolved since the last round
Correctness ✅
Security / performance
Minor (non-blocking, unchanged from last time)
VerdictCorrect, minimal, well-tested, and consistent with existing patterns in the file. All review feedback addressed. LGTM — nothing blocking from me. 👍 Note: I reviewed against the current branch HEAD and could not run the Gradle build/tests here, so I did not execute |
|
Claude finished @joashrajin's task in 45s —— View job Re-review: Fix Up Next episodes being auto-archived by per-podcast limit
I re-reviewed after the latest push. Since my last pass the only change is Correctness ✅
Performance ✅
Security ✅
Test ✅
Minor (non-blocking, unchanged)
VerdictCorrect, minimal, well-tested, and consistent with existing patterns in the file. All prior review feedback (both Copilot threads + CHANGELOG) is resolved. LGTM — nothing blocking from me. 👍 Note: I reviewed against the current branch HEAD and could not run the Gradle build/tests here, so I did not execute |
✅ Verified on-deviceRan the connected instrumented test on a physical device (not just the Buildkite emulator):
So the older episode queued behind the currently-playing one survives the over-limit archive sweep on real hardware, and none of the other archive paths (inactive / played / starred / episode-limit) regressed. |
|
Version |
Description
checkPodcastForEpisodeLimitBlockingfiltered out only the currently-playing episode (playbackManager.getCurrentEpisode(), which is the top of Up Next). Any episode queued behind the current one in Up Next could still be archived when a newer episode arrived for the same podcast and tripped the per-podcast limit during a background refresh.User-visible effect: when a user has a podcast set to
Auto add to Up Next+ a download/episode limit of 1, an episode queued for later is silently marked as played the next time a new episode arrives via background refresh. The Zendesk reporter perceived this as "the episode I was listening to" being marked played mid-listening.Fix: replace the
getCurrentEpisode().uuid != it.uuidfilter with!playbackManager.upNextQueue.contains(it.uuid). The full-queue check is already the pattern used byepisodeCanBeCleanedUpin the same file (line 886), so this aligns the over-limit path with the existing precedent. The new filter is null-safe to preserve the existing test-onlyplaybackManager = nullbehaviour.Added an
androidTestregression that mirrors the bug: episode limit = 1, two episodes for the same podcast, the older one queued behind a newer one in Up Next. The legacy filter archives the older episode; with this change it stays.Fixes PCDROID-571. User-reported via Zendesk #11253136, with the customer's debug log showing the exact
Auto archiving episode over limit 1 …→Download deleted … Source: UNKNOWNsequence inside a backgroundRefreshPodcastsTaskcycle.Testing Instructions
Auto DownloadandAuto add to Up Next.Or, run the new
androidTest:Checklist
./gradlew spotlessApplyto automatically apply formatting/linting)modules/services/localization/src/main/res/values/strings.xml