{"record":{"id":"73f004e050ea9c4c","repo":"yt-dlp/yt-dlp","slug":"failed-to-retrieve-video-list-for-page-page-1","errorCode":null,"errorMessage":"Failed to retrieve video list for page {page + 1}","messagePattern":"Failed to retrieve video list for page (.+?)","errorType":"exception","errorClass":"ExtractorError","httpStatus":null,"severity":"error","filePath":"yt_dlp/extractor/murrtube.py","lineNumber":131,"sourceCode":"    def _fetch_page(self, username, user_id, page):\n        data = self._download_gql(username, {\n            'operationName': 'Media',\n            'variables': {\n                'limit': self._PAGE_SIZE,\n                'offset': page * self._PAGE_SIZE,\n                'sort': 'latest',\n                'userId': user_id,\n            },\n            'query': '''\\\nquery Media($q: String, $sort: String, $userId: ID, $offset: Int!, $limit: Int!) {\n  media(q: $q, sort: $sort, userId: $userId, offset: $offset, limit: $limit) {\n    id\n    __typename\n  }\n}'''},\n            f'Downloading page {page + 1}')\n        if data is None:\n            raise ExtractorError(f'Failed to retrieve video list for page {page + 1}')\n\n        media = data['media']\n\n        for entry in media:\n            yield self.url_result('murrtube:{}'.format(entry['id']), MurrtubeIE.ie_key())\n\n    def _real_extract(self, url):\n        username = self._match_id(url)\n        data = self._download_gql(username, {\n            'operationName': 'User',\n            'variables': {\n                'id': username,\n            },\n            'query': '''\\\nquery User($id: ID!) {\n  user(id: $id) {\n    id\n    __typename","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/yt-dlp/yt-dlp/blob/81ecd58b1394793e6da9998cc19fdb45657f1685/yt_dlp/extractor/murrtube.py#L113-L149","documentation":"Thrown by MurrtubeUserIE._fetch_page (yt_dlp/extractor/murrtube.py:131) while paginating a user's uploads. The GraphQL POST to https://murrtube.net/graphql (operationName 'Media', limit 10, offset page*10) returned HTTP 200 whose 'data' key is null - the standard GraphQL error envelope - so _download_gql returns None and that page of the video list is unusable. The extractor class is even marked _WORKING = False because the site's API keeps drifting.","triggerScenarios":"POST to murrtube.net/graphql with variables {limit: 10, offset: page*10, sort: 'latest', userId: <id>}; the response body is {'errors': [...], 'data': null} instead of containing a 'media' array - invalid userId, WAF/Cloudflare challenge JSON, or a changed schema. Offsets past the end of a user's media can also return null data.","commonSituations":"Site API schema drift (extractor already flagged broken via _WORKING = False); stale yt-dlp version; missing age-check session because _real_initialize could not set the murrtube.net cookies; datacenter IP served a challenge instead of GraphQL data.","solutions":["Update yt-dlp to the latest nightly (python -m pip -U --pre 'yt-dlp[default]'); murrtube is patched often and is currently marked broken","Reproduce the call: POST the 'Media' query to https://murrtube.net/graphql with the same variables and read the 'errors' array to see the real reason","Verify the username is a real profile - the 'User' query in _real_extract must return a user id before pagination starts","Run with --verbose and confirm the age-check step (murrtube.net/accept_age_check) succeeded; clear cookies and retry","If the API shape changed, report it with the --verbose log to https://github.com/yt-dlp/yt-dlp/issues"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import json, urllib.request\n\ndef murrtube_media_page_ok(username, user_id, page):\n    op = {'operationName': 'Media', 'variables': {'limit': 10, 'offset': page * 10,\n          'sort': 'latest', 'userId': user_id},\n          'query': 'query Media($q: String, $sort: String, $userId: ID, $offset: Int!, $limit: Int!) { media(q: $q, sort: $sort, userId: $userId, offset: $offset, limit: $limit) { id __typename } }'}\n    req = urllib.request.Request('https://murrtube.net/graphql', data=json.dumps(op).encode(),\n                                 headers={'Content-Type': 'application/json'})\n    with urllib.request.urlopen(req) as r:\n        return json.load(r).get('data') is not None","typeGuard":"def is_graphql_null_data(resp: dict) -> bool:\n    return resp.get('data') is None","tryCatchPattern":"from yt_dlp.utils import ExtractorError\ntry:\n    info = ydl.extract_info(url, download=False)\nexcept ExtractorError as e:\n    if 'Failed to retrieve video list' in str(e):\n        log.warning('murrtube pagination failed (site API drift, extractor is _WORKING=False): %s', e)\n    else:\n        raise","preventionTips":["Check the extractor status before use: MurrtubeUserIE._WORKING is False, so expect breakage and pin a yt-dlp version known to work","Keep yt-dlp on the nightly channel when using rarely-maintained extractors","Extract per-video URLs opportunistically so one failed page does not lose already-yielded entries","Wrap playlist extraction with extract_info(..., process=False) first to cheaply probe whether the site responds"],"tags":["yt-dlp","extractor","graphql","murrtube","pagination"],"backgroundTag":"graphql-null-data-response","analyzedSha":"81ecd58b1394793e6da9998cc19fdb45657f1685","analyzedAt":"2026-08-22T12:21:25.439Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}