jackwener/OpenCLI · error · CommandExecutionError

Xiaoyuzhou transcript download failed with HTTP ${response.s

Error message

Xiaoyuzhou transcript download failed with HTTP ${response.status}${bodyText ? `: ${bodyText}` : ''}

What it means

Thrown by fetchXiaoyuzhouTranscriptBody (clis/xiaoyuzhou/auth.js:276) when the transcript endpoint responded with a non-2xx HTTP status. The library reads the response body and appends it to the message so the server's error payload (rate-limit notice, 403 anti-bot page, 404) is visible. Unlike error 4900, the network round-trip succeeded — the server actively rejected the request.

Source

Thrown at clis/xiaoyuzhou/auth.js:276

export async function fetchXiaoyuzhouTranscriptBody(url, fetchImpl = fetch) {
    let response;
    try {
        response = await fetchImpl(url, {
            method: 'GET',
            headers: {
                'User-Agent': XIAOYUZHOU_DEFAULT_USER_AGENT,
                Accept: '*/*',
                Market: 'AppStore',
            },
            signal: AbortSignal.timeout(20_000),
        });
    }
    catch (error) {
        throw new CommandExecutionError(`Failed to fetch Xiaoyuzhou transcript content: ${getErrorMessage(error)}`);
    }
    const bodyText = await response.text();
    if (!response.ok) {
        throw new CommandExecutionError(`Xiaoyuzhou transcript download failed with HTTP ${response.status}${bodyText ? `: ${bodyText}` : ''}`);
    }
    return bodyText;
}

export function extractTranscriptText(transcriptBody) {
    let parsed;
    try {
        parsed = JSON.parse(transcriptBody);
    }
    catch {
        return { text: '', segmentCount: 0 };
    }
    let items = [];
    if (Array.isArray(parsed)) {
        items = parsed;
    }
    else if (parsed && typeof parsed === 'object') {
        for (const key of ['segments', 'data', 'transcript', 'items']) {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read the HTTP status and body appended to the message to determine server-side cause
  2. For 403, update the User-Agent / Market headers in auth.js:263-266 to match the current official app values
  3. For 429, add backoff/delay between transcript requests and retry later
  4. For 404, verify the episode ID/URL is correct and that a transcript actually exists for it
  5. For 5xx, retry after a delay — it is a server-side outage

Example fix

// before
const body = await fetchXiaoyuzhouTranscriptBody(url);
// after
try {
  const body = await fetchXiaoyuzhouTranscriptBody(url);
} catch (error) {
  const m = /HTTP (\d{3})/.exec(error.message);
  if (m && m[1] === '429') {
    await new Promise(r => setTimeout(r, 5000));
    return fetchXiaoyuzhouTranscriptBody(url);
  }
  throw error;
}
Defensive patterns

Strategy: retry

Validate before calling

const probe = await fetch(url, { method: 'HEAD' });
if (!probe.ok) console.warn(`Endpoint preflight returned HTTP ${probe.status}; expect failure.`);

Type guard

null

Try / catch

try {
  const body = await fetchXiaoyuzhouTranscriptBody(url);
} catch (error) {
  const status = Number(/HTTP (\d{3})/.exec(error.message)?.[1] ?? 0);
  if (status === 429 || status >= 500) { /* wait and retry */ }
  else if (status === 403) { /* fix headers/User-Agent */ }
  else throw error; // 404 etc. is permanent
}

Prevention

When it happens

Trigger: fetchXiaoyuzhouTranscriptBody(url) returns response.ok === false — e.g. HTTP 403 (CDN/WAF blocking the default User-Agent or the AppStore Market header being rejected), 404 (episode transcript no longer exists / wrong URL), 429 (rate limited), or 5xx (server outage).

Common situations: Scraping many transcripts quickly and hitting rate limits; Xiaoyuzhou changing their CDN rules so the hardcoded User-Agent ('Xiaoyuzhou Default User Agent'/AppStore Market) is blocked; passing an episode ID whose transcript was removed, yielding a 404; transient 502/503 during server incidents.

Related errors


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