jackwener/OpenCLI · error · CommandExecutionError

Malformed toutiao recommend row: group_id/source_url mismatc

Error message

Malformed toutiao recommend row: group_id/source_url mismatch for ${JSON.stringify(rawGroupId)}.

What it means

buildGroupIdentity cross-checks the explicit group_id against the article id extracted from the source_url path. When both exist and differ, the row is internally inconsistent — trusting either value could produce a wrong canonical link — so it throws this CommandExecutionError naming the conflicting group_id.

Source

Thrown at clis/toutiao/utils.js:234

}

function articleIdFromToutiaoPath(pathname) {
    return String(pathname || '').match(/^\/(?:group|article)\/([A-Za-z0-9_-]+)\/?$/)?.[1] || '';
}

function buildGroupIdentity(sourceUrl, rawGroupId) {
    const source = firstPartyToutiaoUrl(sourceUrl);
    const sourceId = source ? articleIdFromToutiaoPath(source.pathname) : '';
    const explicitId = trimOrNull(rawGroupId);
    const groupId = explicitId || trimOrNull(sourceId);
    if (!groupId) {
        throw new CommandExecutionError('Malformed toutiao recommend row: missing group_id and article source_url.');
    }
    if (!/^[A-Za-z0-9_-]+$/.test(groupId)) {
        throw new CommandExecutionError(`Malformed toutiao recommend row: invalid group_id ${JSON.stringify(rawGroupId)}.`);
    }
    if (sourceId && explicitId && explicitId !== sourceId) {
        throw new CommandExecutionError(`Malformed toutiao recommend row: group_id/source_url mismatch for ${JSON.stringify(rawGroupId)}.`);
    }
    return {
        groupId,
        url: `https://www.toutiao.com/group/${groupId}/`,
    };
}

function formatBehotTime(value) {
    const seconds = Number(value);
    if (!Number.isFinite(seconds) || seconds <= 0) return null;
    return `${new Date(seconds * 1000).toISOString().slice(0, 19)}Z`;
}

/**
 * Project a row from the public toutiao channel feed into stable shape.
 *
 * Sponsored rows are dropped so an agent never reads an ad as editorial
 * content; absent counts stay null rather than being coerced to 0.

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Determine which value is correct (fetch both ids and compare content) and fix the row's data.
  2. Re-derive group_id from source_url by omitting the explicit group_id argument, letting the URL id win.
  3. Flag mismatched rows for review instead of silently picking one side.
  4. Add an upstream-consistency check in your ingestion pipeline that runs before buildGroupIdentity.

Example fix

// before
buildGroupIdentity(row.source_url, row.group_id); // stale group_id
// after
buildGroupIdentity(row.source_url, null); // trust source_url-derived id
Defensive patterns

Strategy: validation

Validate before calling

const fromUrl = String(row.source_url || '').replace(/^https?:\/\/[^/]+/, '').match(/^\/(?:group|article)\/([A-Za-z0-9_-]+)\/?$/)?.[1];
if (row.group_id && fromUrl && row.group_id !== fromUrl) flagMismatch(row);

Type guard

const idsConsistent = (row) => { const fromUrl = String(row?.source_url || '').replace(/^https?:\/\/[^/]+/, '').match(/^\/(?:group|article)\/([A-Za-z0-9_-]+)\/?$/)?.[1]; return !row?.group_id || !fromUrl || row.group_id === fromUrl; };

Try / catch

try {
  const identity = buildGroupIdentity(row.source_url, row.group_id);
} catch (e) {
  if (e instanceof CommandExecutionError && e.message.includes('mismatch')) return quarantineRow(row, e.message);
  throw e;
}

Prevention

When it happens

Trigger: A row where group_id='123' but source_url='https://www.toutiao.com/article/456/'; copying a group_id from a different row than its URL; merging datasets where ids and URLs were not kept in sync.

Common situations: Faulty ETL joins mapping ids to the wrong rows; upstream feed republishing an article under a new id while the old group_id remains cached; hand-edited fixtures.

Understand the failure class

Related errors


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