{"record":{"id":"f94523413992cf46","repo":"jackwener/OpenCLI","slug":"failed-to-get-caption-info-typeof-captiondata","errorCode":null,"errorMessage":"`Failed to get caption info: ${typeof captionData === 'string' ? captionData : 'malformed response'}`","messagePattern":"`Failed to get caption info: (.+?)`","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/youtube/transcript.js","lineNumber":443,"sourceCode":"            || tracks.find(t => t.languageCode.startsWith(langPref));\n        }\n        if (!track) {\n          track = tracks.find(t => t.kind !== 'asr') || tracks[0];\n        }\n\n        return {\n          captionUrl: track.baseUrl,\n          language: track.languageCode,\n          kind: track.kind || 'manual',\n          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        }","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/youtube/transcript.js#L425-L461","documentation":"Before trusting the caption info collected from the page, the segments command checks that captionData is a non-null, non-array object. If it is a string (an in-page error message) or any other malformed shape, this error reports either the string itself or 'malformed response'. It guards the boundary between the injected page script and the Node CLI.","triggerScenarios":"`segments` -> page.evaluate of the caption-info script returns a string (the script hit an error and returned a message) or null/undefined/array instead of the expected caption-info object.","commonSituations":"YouTube DOM changes breaking the captionTracks lookup script; videos with caption UI absent entirely; consent walls preventing player data; automated tab not fully loaded when evaluate runs.","solutions":["If the message contains an in-page error string, use it to identify the page-side failure.","Wait for the player to be fully initialized before running extraction (add page-load readiness checks).","Update the CLI so the injected caption-info script matches current YouTube player markup.","Retry in a fresh session; consent/cookie walls often cause the script to return error strings."],"exampleFix":"// before\nconst captionData = unwrapBrowserResult(await page.evaluate(`...`));\n// after\nconst captionData = unwrapBrowserResult(await page.evaluate(`...`));\nif (typeof captionData === 'string') {\n  console.error('page script reported:', captionData);\n}","handlingStrategy":"type-guard","validationCode":"const captionData = unwrapBrowserResult(await page.evaluate(`...`));\nif (captionData == null || typeof captionData !== 'object' || Array.isArray(captionData)) {\n  // handle malformed/string error response before proceeding\n}","typeGuard":"function isCaptionInfo(v) {\n  return !!v && typeof v === 'object' && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  // caption info extraction\n} catch (err) {\n  if (err.message.startsWith('Failed to get caption info')) {\n    console.error('page reported:', err.message); // string payloads carry the in-page error text\n  }\n}","preventionTips":["Wait for full player initialization before evaluating extraction scripts","Complete consent/cookie flows in the automated browser first","Log string-returning evaluates — they usually carry the real page-side error","Update injected scripts whenever YouTube player markup changes"],"tags":["youtube","browser-automation","type-guard","malformed-response"],"backgroundTag":"malformed-response-payload","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}