jackwener/OpenCLI · error · CommandExecutionError

`Malformed ${source} payload`

Error message

`Malformed ${source} payload`

What it means

normalizeSegmentsPayload throws this when the unwrapped browser result is neither null, an array, nor an object carrying an `error` field — i.e. the payload shape is unrecognizable. The `source` label (e.g. 'player caption extraction') is interpolated into the message to identify which extraction step produced the bad payload.

Source

Thrown at clis/youtube/transcript.js:33

import { CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';

function unwrapBrowserResult(value) {
    if (value && typeof value === 'object' && 'session' in value && 'data' in value) {
        return value.data;
    }
    return value;
}

function normalizeSegmentsPayload(value, source, { allowNull = false } = {}) {
    const payload = unwrapBrowserResult(value);
    if (payload == null && allowNull)
        return null;
    if (Array.isArray(payload))
        return payload;
    if (payload && typeof payload === 'object' && payload.error) {
        throw new CommandExecutionError(String(payload.error));
    }
    throw new CommandExecutionError(`Malformed ${source} payload`);
}

function parseJson3Segments(text) {
    let data;
    try {
        data = JSON.parse(text);
    }
    catch (err) {
        throw new CommandExecutionError(`Malformed json3 timedtext response: ${err?.message || err}`);
    }
    if (!Array.isArray(data?.events)) {
        throw new CommandExecutionError('Malformed json3 timedtext response: missing events array');
    }
    const rows = [];
    for (const event of data.events) {
        const startMs = Number(event?.tStartMs || 0);
        const durMs = Number(event?.dDurationMs || 0);
        const segs = Array.isArray(event?.segs) ? event.segs : [];

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Identify which `source` appears in the message and inspect that extraction step's injected script against current YouTube DOM.
  2. Update the CLI so its injected scripts match the current YouTube page structure.
  3. Retry with a fresh browser page/session to rule out transient navigation or half-loaded pages.
  4. Confirm the video URL is a valid watch URL (not a channel/shorts redirect) before invoking extraction.

Example fix

// before
const segments = normalizeSegmentsPayload(playerResult, 'player caption extraction', { allowNull: true });
// after
if (playerResult == null || Array.isArray(playerResult) || (playerResult && typeof playerResult === 'object')) {
  // proceed
} else {
  console.error('unexpected payload shape from browser context');
}
Defensive patterns

Strategy: type-guard

Validate before calling

const unwrapped = unwrapBrowserResult(value);
if (unwrapped != null && !Array.isArray(unwrapped) && (typeof unwrapped !== 'object' || !('error' in unwrapped))) {
  // acceptable only if it matches one of the expected shapes; otherwise abort early
}

Type guard

function isKnownPayloadShape(v) {
  return v == null || Array.isArray(v) || (typeof v === 'object' && !Array.isArray(v));
}

Try / catch

try {
  const segments = normalizeSegmentsPayload(playerResult, 'player caption extraction', { allowNull: true });
} catch (err) {
  if (/Malformed .* payload/.test(err.message)) {
    console.error('unrecognized payload shape from browser; reloading page');
  }
}

Prevention

When it happens

Trigger: `segments` -> `normalizeSegmentsPayload` receives an object without `error` and not an array — e.g. page.evaluate returned undefined/{} because the injected script hit an exception silently, or returned an unexpected wrapper object after a YouTube page change.

Common situations: YouTube changing the player response shape; the injected script returning undefined on pages where the player API is absent; consent/cookie walls causing partial page loads; browser tab navigated away mid-evaluation.

Understand the failure class

Related errors


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