{"record":{"id":"420a1695eaf812ae","repo":"pear-devs/pear-desktop","slug":"playabilitystatus-status-playabilitystatus","errorCode":null,"errorMessage":"[${playabilityStatus.status}] ${playabilityStatus.reason}","messagePattern":"\\[\\$\\{playabilityStatus\\.status\\}\\] \\$\\{playabilityStatus\\.reason\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/plugins/downloader/main/index.ts","lineNumber":388,"sourceCode":"\n  metadata.trackId = trackId;\n\n  const dir =\n    playlistFolder || config.downloadFolder || app.getPath('downloads');\n  const name = `${metadata.artist ? `${metadata.artist} - ` : ''}${\n    metadata.title\n  }`;\n  setName(name);\n\n  let playabilityStatus = info.playability_status;\n  let bypassedResult: YT.VideoInfo;\n  if (playabilityStatus?.status === 'LOGIN_REQUIRED') {\n    // Try to bypass the age restriction\n    bypassedResult = await getAndroidTvInfo(id);\n    playabilityStatus = bypassedResult.playability_status;\n\n    if (playabilityStatus?.status === 'LOGIN_REQUIRED') {\n      throw new Error(\n        `[${playabilityStatus.status}] ${playabilityStatus.reason}`,\n      );\n    }\n\n    info = bypassedResult;\n  }\n\n  if (playabilityStatus?.status === 'UNPLAYABLE') {\n    const errorScreen =\n      playabilityStatus.error_screen as YTNodes.PlayerErrorMessage | null;\n    throw new Error(\n      `[${playabilityStatus.status}] ${errorScreen?.reason.text}: ${errorScreen?.subreason.text}`,\n    );\n  }\n\n  const selectedPreset = config.selectedPreset ?? 'mp3 (256kbps)';\n  let presetSetting: Preset;\n  if (selectedPreset === 'Custom') {","sourceCodeStart":370,"sourceCodeEnd":406,"githubUrl":"https://github.com/pear-devs/pear-desktop/blob/1e2aac5706c870c93ed74ba8727ae8390d3b0fa5/src/plugins/downloader/main/index.ts#L370-L406","documentation":"When YouTube reports playability status LOGIN_REQUIRED, the downloader tries to bypass it by re-fetching track info through the Android TV client (getAndroidTvInfo). If that fallback still reports LOGIN_REQUIRED, it throws an error formatted as '[LOGIN_REQUIRED] <reason>' (typically 'Sign in to confirm your age' or bot-detection messages).","triggerScenarios":"Downloading age-restricted tracks where the TV-client bypass no longer works; YouTube flagging the IP/client as a bot ('Sign in to confirm you're not a bot'); region or account-gated content; the bypass endpoint being deprecated in the installed youtube-ext version.","commonSituations":"YouTube tightening bot/age verification (common waves of 'Sign in to confirm you're not a bot'); downloading from datacenter IPs or VPNs; outdated plugin versions whose Android TV bypass has been blocked; large batch downloads triggering rate-based sign-in walls.","solutions":["Update the downloader plugin (and its youtube-ext dependency) to a version with a working bypass","Use cookies/auth if the plugin supports it so getInfo is authenticated","Slow down batch downloads and avoid VPN/datacenter IPs to reduce bot-flagging","For age-restricted content, sign in via a supported mechanism or skip the track"],"exampleFix":"// before\nresults.push(await downloader.downloadSong(url)); // one failure kills the batch\n\n// after\nfor (const url of urls) {\n  try { await downloader.downloadSong(url); }\n  catch (e) {\n    if (e.message.includes('LOGIN_REQUIRED')) skipped.push({url, reason: e.message});\n    else throw e;\n  }\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await downloadSong(url); } catch (e) {\n  if (/\\[LOGIN_REQUIRED\\]/.test(e.message)) { skipped.push({ url, reason: e.message }); return; }\n  throw e;\n}","preventionTips":["Keep the downloader plugin and youtube-ext updated — bypasses get patched frequently","Authenticate via cookies if the plugin supports it","Rate-limit batch downloads; avoid VPN/datacenter IPs to reduce bot-flagging"],"tags":["downloader","youtube","age-restriction","bot-detection","login-required","bypass"],"backgroundTag":"platform-login-required","analyzedSha":"1e2aac5706c870c93ed74ba8727ae8390d3b0fa5","analyzedAt":"2026-08-27T20:01:08.614Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}