Skip to content

fix(compare): exclude YouTube ghosts from possibly_missing_from_ytmusic - #11

Merged
zeikar merged 1 commit into
mainfrom
fix/0.6.1-ghost-cross-prop-collision
May 12, 2026
Merged

fix(compare): exclude YouTube ghosts from possibly_missing_from_ytmusic#11
zeikar merged 1 commit into
mainfrom
fix/0.6.1-ghost-cross-prop-collision

Conversation

@zeikar

@zeikar zeikar commented May 12, 2026

Copy link
Copy Markdown
Owner

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:

  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 by build_unavailable_video_items) remains the sole source of truth for ghost cleanup.

Live verification

Bucket Before fix After fix Δ
possibly_missing_from_ytmusic 34 16 -18
unavailable_video 18 18 0

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 . — clean
  • uv run ruff check — clean
  • New tests: ghost excluded from possibly_missing / unknown availability still surfaces / explicit available surfaces (regression coverage for the filter)
  • Live verification: possibly_missing dropped from 34 → 16 on real account, exactly matching the 18-ghost overlap

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.
Copilot AI review requested due to automatic review settings May 12, 2026 13:12
@zeikar
zeikar merged commit fbea5b4 into main May 12, 2026
3 checks passed
@zeikar
zeikar deleted the fix/0.6.1-ghost-cross-prop-collision branch May 12, 2026 13:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 False out of the possibly_missing_from_ytmusic bucket in compare_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/True still 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
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.

2 participants