{"record":{"id":"9994650d22ccd54b","repo":"jackwener/OpenCLI","slug":"msg","errorCode":null,"errorMessage":"msg","messagePattern":"msg","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/youtube/transcript.js","lineNumber":453,"sourceCode":"          available,\n          requestedLang: langPref || null,\n          langMatched: !!(langPref && track.languageCode === langPref),\n          langPrefixMatched: !!(langPref && track.languageCode !== langPref && track.languageCode.startsWith(langPref))\n        };\n      })()\n    `));\n        if (!segments && (!captionData || typeof captionData !== 'object' || Array.isArray(captionData))) {\n            throw new CommandExecutionError(`Failed to get caption info: ${typeof captionData === 'string' ? captionData : 'malformed response'}`);\n        }\n        if (captionData?.error) {\n            const msg = `${captionData.error}${captionData.available ? ' (available: ' + captionData.available.join(', ') + ')' : ''}`;\n            // \"No captions available\" 是合法 empty 数据条件（作者没开字幕 + YT 没自动生成），\n            // 与 bilibili subtitle 的 EmptyResultError 同模式。下游应按 code EMPTY_RESULT 跳过\n            // 重试和 softFail 计数。其它 error（HTTP / parse / 短暂空响应）仍按 fetch 失败抛。\n            if (captionData.error === 'No captions available for this video') {\n                throw new EmptyResultError('youtube transcript', '该视频没有字幕（作者未开启 + 无自动字幕）。');\n            }\n            throw new CommandExecutionError(msg);\n        }\n        if (!segments && typeof captionData?.captionUrl !== 'string') {\n            throw new CommandExecutionError('Malformed caption info payload');\n        }\n        // Warn if --lang was specified but not matched\n        if (captionData?.requestedLang && !captionData.langMatched && !captionData.langPrefixMatched) {\n            console.error(`Warning: --lang \"${captionData.requestedLang}\" not found. Using \"${captionData.language}\" instead. Available: ${captionData.available.join(', ')}`);\n        }\n        // Step 2: Fetch caption XML and parse segments\n        // Ensure caption URL requests srv3 XML format — YouTube may return empty\n        // responses when no explicit format is specified.\n        if (!segments) {\n            const originalCaptionUrl = captionData.captionUrl;\n            let captionUrl = originalCaptionUrl;\n            if (!/[&?]fmt=/.test(originalCaptionUrl)) {\n                captionUrl = originalCaptionUrl + (originalCaptionUrl.includes('?') ? '&' : '?') + 'fmt=srv3';\n            }\n            segments = normalizeSegmentsPayload(await page.evaluate(`","sourceCodeStart":435,"sourceCodeEnd":471,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/youtube/transcript.js#L435-L471","documentation":"For any captionData.error other than the recognized 'No captions available' case, the CLI throws CommandExecutionError with the page-supplied error message (msg), which may include the list of available caption tracks. This represents a genuine fetch/parse/HTTP failure in obtaining caption info, as opposed to a legitimate empty result.","triggerScenarios":"`segments` where the injected caption-info script returns {error: <anything else>} — e.g. HTTP failures fetching the player response, transient empty responses, or page-side exceptions other than the no-captions sentinel.","commonSituations":"Rate limiting or temporary YouTube errors; network glitches in the automated browser; player API responses failing mid-SPA navigation; video unavailable/deleted pages.","solutions":["Read msg — it may include available tracks or the underlying HTTP reason — and address that cause.","Retry after a short backoff; the source comment classifies these as transient fetch failures eligible for retry.","Verify the video is playable in the automated browser (not deleted/region-blocked).","Update the CLI if the message indicates a page-structure mismatch."],"exampleFix":"// before\nconst segments = await getSegments(videoId);\n// after\nconst segments = await retry(() => getSegments(videoId), { retries: 2, backoffMs: 1000 }); // transient caption fetch errors only","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const segments = await getSegments(videoId);\n} catch (err) {\n  if (!(err instanceof EmptyResultError)) {\n    // transient fetch/parse failure per source comment — retry with backoff\n    await sleep(1000);\n    return getSegments(videoId);\n  }\n  throw err;\n}","preventionTips":["Apply bounded retries with backoff — the CLI classifies these as transient fetch failures","Check the message: it may list available tracks or an HTTP reason","Verify the video is playable in the automated browser before extracting","Avoid hammering YouTube concurrently to reduce rate-limit-triggered errors"],"tags":["youtube","fetch-failure","retryable","browser-automation"],"backgroundTag":"transient-fetch-failure","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}