{"record":{"id":"8aa0977932f8e287","repo":"jackwener/OpenCLI","slug":"malformed-json3-timedtext-response-err-messag","errorCode":null,"errorMessage":"`Malformed json3 timedtext response: ${err?.message || err}`","messagePattern":"`Malformed json3 timedtext response: (.+?)`","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/youtube/transcript.js","lineNumber":42,"sourceCode":"function normalizeSegmentsPayload(value, source, { allowNull = false } = {}) {\n    const payload = unwrapBrowserResult(value);\n    if (payload == null && allowNull)\n        return null;\n    if (Array.isArray(payload))\n        return payload;\n    if (payload && typeof payload === 'object' && payload.error) {\n        throw new CommandExecutionError(String(payload.error));\n    }\n    throw new CommandExecutionError(`Malformed ${source} payload`);\n}\n\nfunction parseJson3Segments(text) {\n    let data;\n    try {\n        data = JSON.parse(text);\n    }\n    catch (err) {\n        throw new CommandExecutionError(`Malformed json3 timedtext response: ${err?.message || err}`);\n    }\n    if (!Array.isArray(data?.events)) {\n        throw new CommandExecutionError('Malformed json3 timedtext response: missing events array');\n    }\n    const rows = [];\n    for (const event of data.events) {\n        const startMs = Number(event?.tStartMs || 0);\n        const durMs = Number(event?.dDurationMs || 0);\n        const segs = Array.isArray(event?.segs) ? event.segs : [];\n        const line = segs.map(seg => seg?.utf8 || '').join('').replace(/\\s+/g, ' ').trim();\n        if (!line)\n            continue;\n        rows.push({\n            start: startMs / 1000,\n            end: (startMs + durMs) / 1000,\n            text: line,\n        });\n    }","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/youtube/transcript.js#L24-L60","documentation":"parseJson3Segments parses YouTube's json3 timedtext format (the caption track format fetched from /api/timedtext?fmt=json3). If JSON.parse fails on the response body, this error wraps the underlying parse message. It signals the fetched caption body was not valid json3 JSON (truncated, HTML error page, or encoding issue).","triggerScenarios":"`parsed` -> `parseJson3Segments(text)` where text is a captured or fetched timedtext body that is not valid JSON — e.g. an HTML error/consent page, empty string with garbage, or truncated network responsePreview.","commonSituations":"YouTube serving a bot-check or consent interstitial instead of caption data; network capture storing partial response bodies; proxies/firewalls rewriting responses; expired signed timedtext URLs returning error pages.","solutions":["Log the raw body prefix to see what was actually returned (HTML vs truncated JSON).","Re-fetch captions with a fresh request — signed timedtext URLs expire and stale captures may hold dead responses.","Ensure the browser session passed any consent/bot checks before capture (complete cookie consent in the automated tab).","Fall back to the player captionTracks extraction path if network capture keeps yielding non-JSON bodies."],"exampleFix":"// before\nconst segments = parseJson3Segments(body);\n// after\nlet segments;\ntry {\n  segments = parseJson3Segments(body);\n} catch (err) {\n  console.error('timedtext body head:', String(body).slice(0, 200));\n  throw err;\n}","handlingStrategy":"validation","validationCode":"const body = typeof entry?.responsePreview === 'string' ? entry.responsePreview : '';\nif (!body.trim().startsWith('{')) {\n  // not JSON — skip before calling parseJson3Segments\n}","typeGuard":"function looksLikeJson3(text) {\n  if (typeof text !== 'string' || !text.trim().startsWith('{')) return false;\n  try { const d = JSON.parse(text); return Array.isArray(d?.events); } catch { return false; }\n}","tryCatchPattern":"try {\n  const segments = parseJson3Segments(body);\n} catch (err) {\n  if (err.message.startsWith('Malformed json3 timedtext response')) {\n    console.error('body head:', String(body).slice(0, 200));\n  }\n}","preventionTips":["Inspect raw response bodies when capture parsing fails","Complete consent/bot checks in the automated tab before requests fire","Re-fetch timedtext with fresh signed URLs rather than reusing stale captures","Fall back to the player captionTracks path on repeated parse failures"],"tags":["youtube","json","parse-error","timedtext"],"backgroundTag":"json-parse-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}