jackwener/OpenCLI · error · CommandExecutionError

Xiaoyuzhou playback progress returned duplicate eid ${eid}

Error message

Xiaoyuzhou playback progress returned duplicate eid ${eid}

What it means

parseProgressRows joins a playback-progress API response against episodes already collected from history. Every progress row must reference a requested episode (eid), and each eid may appear at most once. This error means the progress endpoint returned the same eid twice, so the join would be ambiguous about which row's progress is authoritative.

Source

Thrown at clis/xiaoyuzhou/history.js:122

}

function parseProgressRows(response, episodes) {
    if (!Array.isArray(response?.data)) {
        throw new CommandExecutionError('Xiaoyuzhou playback progress returned an unexpected response shape');
    }
    const requested = new Map(episodes.map((episode) => [episode.eid, episode]));
    const progressById = new Map();
    for (const [index, row] of response.data.entries()) {
        if (!isRecord(row)) {
            throw new CommandExecutionError(`Xiaoyuzhou playback progress row ${index + 1} is malformed`);
        }
        const eid = requiredId(row.eid, `progress eid in row ${index + 1}`);
        const episode = requested.get(eid);
        if (!episode) {
            throw new CommandExecutionError(`Xiaoyuzhou playback progress returned unrequested eid ${eid}`);
        }
        if (progressById.has(eid)) {
            throw new CommandExecutionError(`Xiaoyuzhou playback progress returned duplicate eid ${eid}`);
        }
        const pid = requiredId(row.pid, `progress pid in row ${index + 1}`);
        if (pid !== episode.pid) {
            throw new CommandExecutionError(`Xiaoyuzhou playback progress pid did not match history eid ${eid}`);
        }
        const progressSec = optionalSeconds(row.progress, `progress in row ${index + 1}`);
        if (progressSec !== null && episode.durationSec !== null && progressSec > episode.durationSec) {
            throw new CommandExecutionError(`Xiaoyuzhou playback progress exceeded duration for eid ${eid}`);
        }
        progressById.set(eid, {
            progressSec,
            playedAt: optionalIsoTime(row.playedAt, `playedAt in row ${index + 1}`),
        });
    }
    for (const episode of episodes) {
        if (!progressById.has(episode.eid)) {
            throw new CommandExecutionError(
                `Xiaoyuzhou playback progress omitted requested eid ${episode.eid}; the history join is incomplete`,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Deduplicate progress rows by eid before calling parseProgressRows, keeping the row with the latest playedAt or highest progress.
  2. Check whether your pagination loop overlaps pages and skip already-seen eids between pages.
  3. Verify with a raw request that duplicates come from the server, then report/patch against the upstream change.
  4. If using cached or recorded fixtures, regenerate them from a live response.

Example fix

// before
const rows = progressResponse.data.list;
return parseProgressRows(rows, episodes);
// after
const seen = new Set();
const rows = progressResponse.data.list.filter(r => {
  if (seen.has(r.eid)) return false;
  seen.add(r.eid);
  return true;
});
return parseProgressRows(rows, episodes);
Defensive patterns

Strategy: validation

Validate before calling

function assertUniqueEids(rows) {
  const seen = new Set();
  for (const r of rows) {
    if (seen.has(r.eid)) throw new Error(`duplicate eid ${r.eid}`);
    seen.add(r.eid);
  }
}
assertUniqueEids(progressRows);

Type guard

const hasUniqueEids = (rows) => new Set(rows.map(r => r.eid)).size === rows.length;

Prevention

When it happens

Trigger: The Xiaoyuzhou progress API returns two rows with the same eid for one fetch, e.g. a server-side bug, duplicated page data, or overlapping pagination whose rows are concatenated client-side before parsing.

Common situations: Upstream API schema changes introducing duplicate rows; mocking/recording proxies replaying responses; hand-crafted fixtures with repeated eids; paginated responses overlapping so the same progress row is fetched twice.

Related errors


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