jackwener/OpenCLI · error · CommandExecutionError
Malformed caption info payload
Error message
Malformed caption info payload
What it means
After passing the shape and error checks, if no segments were already extracted and captionData.captionUrl is not a string, the CLI throws this fixed message. The caption info object was structurally valid but lacked the URL needed to fetch the caption XML (Step 2), so the payload is incomplete.
Source
Thrown at clis/youtube/transcript.js:456
langPrefixMatched: !!(langPref && track.languageCode !== langPref && track.languageCode.startsWith(langPref))
};
})()
`));
if (!segments && (!captionData || typeof captionData !== 'object' || Array.isArray(captionData))) {
throw new CommandExecutionError(`Failed to get caption info: ${typeof captionData === 'string' ? captionData : 'malformed response'}`);
}
if (captionData?.error) {
const msg = `${captionData.error}${captionData.available ? ' (available: ' + captionData.available.join(', ') + ')' : ''}`;
// "No captions available" 是合法 empty 数据条件(作者没开字幕 + YT 没自动生成),
// 与 bilibili subtitle 的 EmptyResultError 同模式。下游应按 code EMPTY_RESULT 跳过
// 重试和 softFail 计数。其它 error(HTTP / parse / 短暂空响应)仍按 fetch 失败抛。
if (captionData.error === 'No captions available for this video') {
throw new EmptyResultError('youtube transcript', '该视频没有字幕(作者未开启 + 无自动字幕)。');
}
throw new CommandExecutionError(msg);
}
if (!segments && typeof captionData?.captionUrl !== 'string') {
throw new CommandExecutionError('Malformed caption info payload');
}
// Warn if --lang was specified but not matched
if (captionData?.requestedLang && !captionData.langMatched && !captionData.langPrefixMatched) {
console.error(`Warning: --lang "${captionData.requestedLang}" not found. Using "${captionData.language}" instead. Available: ${captionData.available.join(', ')}`);
}
// Step 2: Fetch caption XML and parse segments
// Ensure caption URL requests srv3 XML format — YouTube may return empty
// responses when no explicit format is specified.
if (!segments) {
const originalCaptionUrl = captionData.captionUrl;
let captionUrl = originalCaptionUrl;
if (!/[&?]fmt=/.test(originalCaptionUrl)) {
captionUrl = originalCaptionUrl + (originalCaptionUrl.includes('?') ? '&' : '?') + 'fmt=srv3';
}
segments = normalizeSegmentsPayload(await page.evaluate(`
(async () => {
async function fetchCaptionXml(url) {
const resp = await fetch(url);View on GitHub (pinned to 49907e53dc)
Solutions
- Update the CLI so the caption-info script reads the current caption track URL field.
- Check captionData keys (log them) to see where the URL now lives in YouTube's response.
- Retry with a fresh page load so the player response is complete.
- Use the network-capture fallback (timedtext requests) instead of the captionUrl path.
Example fix
// before
if (!captionData?.captionUrl) throw new Error('no url');
// after
if (!captionData?.captionUrl) {
console.error('caption info keys:', Object.keys(captionData));
throw new Error('Malformed caption info payload');
} Defensive patterns
Strategy: validation
Validate before calling
if (captionData && typeof captionData === 'object' && typeof captionData.captionUrl !== 'string') {
console.error('caption info missing captionUrl, keys:', Object.keys(captionData));
} Type guard
function hasCaptionUrl(v) {
return !!v && typeof v === 'object' && typeof v.captionUrl === 'string' && v.captionUrl.length > 0;
} Try / catch
try {
// step 2: fetch caption XML via captionUrl
} catch (err) {
if (err.message === 'Malformed caption info payload') {
console.error('caption info lacked captionUrl — schema drift likely');
}
} Prevention
- Keep the CLI in sync with how YouTube exposes caption track baseUrls
- Log captionData keys when the URL is missing to spot schema drift
- Retry after a full page reload so the player response is complete
- Prefer the network-capture path when captionUrl is absent
When it happens
Trigger: `segments` where the caption-info script returned a well-formed object with captionTracks metadata but no captionUrl (and the earlier player/capture extraction paths yielded no segments).
Common situations: YouTube changes to how caption track baseUrl is exposed; partial player responses in SPA-navigated tabs; version skew between the CLI's injected script and current YouTube internals.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- `Malformed ${source} payload`
- Malformed json3 timedtext response: missing events array
- Booking.com extractor returned malformed items
- Instagram media metadata returned malformed result
- Sales Navigator messaging thread API returned malformed payl
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/177fb927096ea28d.
Report an issue: GitHub.