From c223e57463c44273b83bcd15a079c20523559517 Mon Sep 17 00:00:00 2001 From: Mattias Thall Date: Wed, 27 May 2026 03:07:19 +0200 Subject: [PATCH] fix: prevent concurrent yt-dlp sessions that invalidate cookies Three code paths could fire yt-dlp immediately (polite=False) while a download was already running, causing YouTube to see two simultaneous authenticated sessions and invalidate the cookie: - search.py: live yt-dlp fallback now skipped while any download is active - downloads.py: _ensure_video uses polite=True so it waits for active downloads to finish before fetching metadata for an unknown video - channels.py: follow_by_url uses polite=True when fetching metadata for a brand-new channel Added is_download_active() helper to ytdlp.py to expose the active download state without importing private globals. Co-Authored-By: Claude Sonnet 4.6 --- backend/routers/channels.py | 2 +- backend/routers/downloads.py | 2 +- backend/routers/search.py | 5 +++-- backend/services/ytdlp.py | 5 +++++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/backend/routers/channels.py b/backend/routers/channels.py index 65d0de1..18362fd 100644 --- a/backend/routers/channels.py +++ b/backend/routers/channels.py @@ -1185,7 +1185,7 @@ def follow_by_url( channel = db.query(Channel).filter_by(youtube_channel_id=yt_channel_id).first() if not channel: - meta = ytdlp.fetch_channel_metadata(yt_channel_id, max_videos=30) + meta = ytdlp.fetch_channel_metadata(yt_channel_id, max_videos=30, polite=True) if not meta or not meta.get("channel"): raise HTTPException(status_code=404, detail="Channel not found on YouTube") ch_data = meta["channel"] diff --git a/backend/routers/downloads.py b/backend/routers/downloads.py index 3615428..3dcbe2b 100644 --- a/backend/routers/downloads.py +++ b/backend/routers/downloads.py @@ -125,7 +125,7 @@ def _ensure_video(db: Session, youtube_video_id: str) -> Video: if video: return video - meta = ytdlp.fetch_video_metadata(youtube_video_id) + meta = ytdlp.fetch_video_metadata(youtube_video_id, polite=True) if not meta: raise HTTPException(status_code=404, detail="Video not found on YouTube") diff --git a/backend/routers/search.py b/backend/routers/search.py index 3fb0e3b..4c1723c 100644 --- a/backend/routers/search.py +++ b/backend/routers/search.py @@ -264,8 +264,9 @@ def search( source = "local" if (video_results or channel_results) else "none" - # Fall back to live yt-dlp search if no local results or explicitly requested - if not video_results or live: + # Fall back to live yt-dlp search if no local results or explicitly requested. + # Skip if a download is active — concurrent yt-dlp sessions invalidate cookies. + if (not video_results or live) and not ytdlp.is_download_active(): try: live_raw = ytdlp.search_youtube(q) live_results = _live_search_to_results(db, current_user.id, live_raw) diff --git a/backend/services/ytdlp.py b/backend/services/ytdlp.py index 90e3320..7679881 100644 --- a/backend/services/ytdlp.py +++ b/backend/services/ytdlp.py @@ -84,6 +84,11 @@ def _meta_run(args: list[str], timeout: int = 60) -> tuple[str, str, int]: _meta_last_call = time.monotonic() +def is_download_active() -> bool: + with _active_downloads_lock: + return _active_downloads > 0 + + def _parse_date(date_str: str | None) -> datetime | None: if not date_str: return None