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

  1. Update the CLI so the caption-info script reads the current caption track URL field.
  2. Check captionData keys (log them) to see where the URL now lives in YouTube's response.
  3. Retry with a fresh page load so the player response is complete.
  4. 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

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.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/177fb927096ea28d. Report an issue: GitHub.