fix(compare): exclude YouTube ghosts from possibly_missing_from_ytmusic - #11
Merged
Merged
Conversation
Real root cause of the "ghosts don't go away after sync" issue observed since 0.4 (28 → 21 → 18 stuck ghosts across multiple sync cycles). Same vid surfaced as TWO findings: - `unavailable_video` (YouTube ghost — is_available=False) - `possibly_missing_from_ytmusic` (matcher missed it on ytmusic side) 0.4 sync ran them in plan-iteration order: 1. `yt_unlike(vid)` → YouTube state: like → none (actually worked) 2. `ytm_like(vid)` → ytmusic adds vid, cross-propagates to YouTube → YouTube state: none → like (reverts the unlike + pollutes ytmusic) Live-confirmed via isolated probe: `ytm.rate_song(vid, LIKE)` re-likes the vid on YouTube within 3 seconds. Fix: `compare_likes` now filters ghosts (`is_available is False`) out of `possibly_missing_from_ytmusic`. Ghosts are not "missing from ytmusic" in a meaningful sense — they're dead videos. `unavailable_video` finding (built separately) remains the sole source of truth for ghost cleanup. Live verification on this account: - Before fix: possibly_missing=34, unavailable=18 (18 vids in BOTH) - After fix: possibly_missing=16, unavailable=18 (no overlap) - Sync plan: 36 non-conflicting actions (was 53 with 18 self-reverting) Also reverts the 0.5s write-call throttle added earlier this session. The throttle was based on a "bulk silent reject" hypothesis that turned out to be wrong — the silent reject was caused by the cross-prop collision, not by rate limiting. Throttle had no effect on the actual bug and is removed. Refs PR #10.
There was a problem hiding this comment.
Pull request overview
This PR adjusts the cross-source like comparison logic so that YouTube “ghost” videos (unavailable/deleted/private/region-blocked) are no longer reported as possibly_missing_from_ytmusic, preventing sync plans from generating conflicting actions that can re-like ghosts via YouTube Music cross-propagation.
Changes:
- Filter YouTube items with
is_available is Falseout of thepossibly_missing_from_ytmusicbucket incompare_likes. - Extend the item duck-typing contract to include
is_available(tri-state) and document the intended semantics. - Add regression tests covering ghost exclusion and ensuring
is_available=None/Truestill surfaces as possibly-missing.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/likesurgeon/compare.py |
Excludes unavailable YouTube videos from possibly_missing_from_ytmusic and documents is_available semantics. |
tests/test_compare.py |
Adds regression tests for ghost filtering and availability tri-state behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+48
to
+52
| # ``is_available`` is only populated for ``youtube_liked_videos`` items | ||
| # (via stage-1 ``videos.list`` ghost detection). ``True`` = playable, | ||
| # ``False`` = ghost (deleted/private/region-blocked), ``None`` = unknown | ||
| # (e.g. unchecked, or ytmusic items where availability isn't tracked). | ||
| is_available: bool | None |
| # destructive conflict in 0.4-0.6 sync — ``yt_unlike`` would unlike the | ||
| # ghost, then ``ytm_like`` on the same vid would cross-propagate the | ||
| # like back to YouTube via ytmusic, reverting the unlike. See | ||
| # ``docs/ARCHITECTURE.md`` (0.6.1 fix). |
Comment on lines
+338
to
+342
| # Exclude YouTube ghosts (``is_available is False``) from possibly_missing: | ||
| # those are dead videos the user wants gone (unavailable_video finding), | ||
| # not "missing from ytmusic" candidates. Surfacing them here triggered a | ||
| # destructive conflict in 0.4-0.6 sync — ``yt_unlike`` would unlike the | ||
| # ghost, then ``ytm_like`` on the same vid would cross-propagate the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
Real root cause of the "ghosts don't go away after sync" issue observed since 0.4 (28 → 21 → 18 stuck ghosts across multiple sync cycles).
Same vid surfaced as TWO findings:
unavailable_video(YouTube ghost —is_available=False)possibly_missing_from_ytmusic(matcher missed it on ytmusic side)0.4 sync ran them in plan-iteration order:
yt_unlike(vid)→ YouTube state:like → none(actually worked)ytm_like(vid)→ ytmusic adds vid, cross-propagates to YouTube → YouTube state:none → like(reverts the unlike + pollutes ytmusic)Live-confirmed via isolated probe:
ytm.rate_song(vid, LIKE)re-likes the vid on YouTube within 3 seconds.Fix
compare_likesnow filters ghosts (is_available is False) out ofpossibly_missing_from_ytmusic. Ghosts are not "missing from ytmusic" in a meaningful sense — they're dead videos.unavailable_videofinding (built separately bybuild_unavailable_video_items) remains the sole source of truth for ghost cleanup.Live verification
Exactly 18 vids removed — perfect overlap with the 18 ghosts. Sync plan goes from 53 actions (with 18 self-reverting collision pairs) to 36 non-conflicting actions.
Also reverts throttle
The 0.5s write-call throttle added earlier this session was based on a "bulk silent reject" hypothesis that turned out to be wrong — the silent reject was the cross-prop collision, not rate limiting. Throttle had no effect on the actual bug; removed.
Test plan
uv run pytest tests/— 286 passed (3 new regression tests)uv run ruff format --check .— cleanuv run ruff check— clean