jackwener/OpenCLI · error · CommandExecutionError

${parsed?.message || parsed?.msg || `Xiaoyuzhou API returned

Error message

${parsed?.message || parsed?.msg || `Xiaoyuzhou API returned service code ${numericCode}`}

What it means

For any parsed service code other than 0, 200, 401, or 403, requestXiaoyuzhouJson throws a CommandExecutionError using the API's own message field (parsed.message or parsed.msg), falling back to a generic code report. This surfaces backend-declared failures (business logic or server errors) to the CLI user.

Source

Thrown at clis/xiaoyuzhou/auth.js:246

        parsed = JSON.parse(bodyText);
    }
    catch (error) {
        throw new CommandExecutionError(`Xiaoyuzhou API returned invalid JSON: ${getErrorMessage(error)}`);
    }
    const serviceCode = parsed?.code;
    if (serviceCode !== undefined && serviceCode !== null) {
        const numericCode = Number(serviceCode);
        if (!Number.isFinite(numericCode)) {
            throw new CommandExecutionError('Xiaoyuzhou API returned an invalid service code');
        }
        if (numericCode === 401 || numericCode === 403) {
            throw createXiaoyuzhouAuthError(`Xiaoyuzhou API rejected the credentials with service code ${numericCode}`);
        }
        if (numericCode !== 0 && numericCode !== 200) {
            throw new CommandExecutionError(
                parsed?.message || parsed?.msg || `Xiaoyuzhou API returned service code ${numericCode}`,
            );
        }
    }
    if (parsed?.success === false) {
        throw new CommandExecutionError(parsed?.message || parsed?.msg || 'Xiaoyuzhou API returned success=false');
    }
    return {
        credentials,
        raw: parsed,
        data: parsed?.data,
    };
}

export async function fetchXiaoyuzhouTranscriptBody(url, fetchImpl = fetch) {
    let response;
    try {
        response = await fetchImpl(url, {
            method: 'GET',
            headers: {
                'User-Agent': XIAOYUZHOU_DEFAULT_USER_AGENT,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read the message field in the error — it comes straight from the API and usually names the actual problem (e.g. not found vs rate limited).
  2. For 404: verify the episode/podcast/feed ID passed by the calling command is correct.
  3. For 429: back off and retry with exponential delay; reduce polling frequency.
  4. For 5xx: retry later; check Xiaoyuzhou service status.
  5. If a new code appears repeatedly, extend requestXiaoyuzhouJson to handle it explicitly.

Example fix

// before: one generic retry for all codes
const data = await requestXiaoyuzhouJson(creds, path, params);
// after: retry only transient codes
try {
  data = await requestXiaoyuzhouJson(creds, path, params);
} catch (e) {
  if (/code (429|5\d\d)/.test(e.message)) {
    await sleep(backoff);
    data = await requestXiaoyuzhouJson(creds, path, params);
  } else throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

// validate inputs the API would 404/400 on before calling
if (!episodeId || !/^[A-Za-z0-9-]+$/.test(episodeId)) {
  throw new Error(`Invalid episodeId: ${episodeId}`);
}

Type guard

function isTransientServiceCode(e) {
  const m = e.message.match(/service code (\d+)/);
  if (!m) return /code (429|5\d\d)/.test(e.message);
  const code = Number(m[1]);
  return code === 429 || code >= 500;
}

Try / catch

for (let attempt = 0; attempt < 3; attempt++) {
  try {
    return await requestXiaoyuzhouJson(creds, path, params);
  } catch (e) {
    if (isTransientServiceCode(e) && attempt < 2) {
      await new Promise(r => setTimeout(r, 2 ** attempt * 1000)); // backoff: 1s, 2s
      continue;
    }
    throw e;
  }
}

Prevention

When it happens

Trigger: parsed.code is a finite number not in {0, 200, 401, 403} — e.g. 404 (unknown episode/podcast id), 429 (rate limited), 5xx (server error) reported inside the JSON envelope by the Xiaoyuzhou backend.

Common situations: Requesting a deleted or wrong episode/podcast ID (404); hitting rate limits after rapid polling (429); upstream Xiaoyuzhou outages returning 500-class codes; API contract changes introducing new code values the CLI doesn't special-case.

Related errors


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