jackwener/OpenCLI · error · CommandExecutionError

Malformed toutiao recommend row: invalid group_id ${JSON.str

Error message

Malformed toutiao recommend row: invalid group_id ${JSON.stringify(rawGroupId)}.

What it means

buildGroupIdentity requires the resolved group id to match /^[A-Za-z0-9_-]+$/. After falling back from explicit group_id to the source_url-derived article id, any id containing other characters (spaces, slashes, CJK, punctuation) fails this check and throws this CommandExecutionError, because the id is used to construct https://www.toutiao.com/group/<id>/.

Source

Thrown at clis/toutiao/utils.js:231

        if (error instanceof CommandExecutionError) throw error;
        throw new CommandExecutionError(`Malformed toutiao recommend row: invalid source_url ${JSON.stringify(raw)}.`);
    }
}

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.

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Sanitize/decode the id first (decodeURIComponent, trim) and re-check it matches [A-Za-z0-9_-]+ before calling.
  2. Verify against the current Toutiao id format — if ids changed upstream, update the validation pattern.
  3. Drop rows with invalid ids and report them; do not attempt to force-strip invalid characters (yields dead links).
  4. If you construct ids yourself, validate with /^[A-Za-z0-9_-]+$/.test(id) before invoking.

Example fix

// before
buildGroupIdentity(url, decodeURIComponent(rawGroupId));
// after
const id = decodeURIComponent(rawGroupId).trim();
if (!/^[A-Za-z0-9_-]+$/.test(id)) throw new SkipRowError(id);
buildGroupIdentity(url, id);
Defensive patterns

Strategy: validation

Validate before calling

const id = String(rawGroupId || '').trim();
if (!/^[A-Za-z0-9_-]+$/.test(id)) throw new TypeError(`invalid group_id format: ${JSON.stringify(rawGroupId)}`);

Type guard

const isValidGroupId = (v) => typeof v === 'string' && /^[A-Za-z0-9_-]+$/.test(v.trim());

Try / catch

try {
  const identity = buildGroupIdentity(url, rawGroupId);
} catch (e) {
  if (e instanceof CommandExecutionError && e.message.includes('invalid group_id')) return dropRow(rawGroupId);
  throw e;
}

Prevention

When it happens

Trigger: A group_id containing URL-encoded or raw special characters, whitespace, or full-width characters; a source_url whose captured path segment includes unexpected characters (e.g. '/article/abc.def/').

Common situations: Upstream changing id formats (numeric ids becoming prefixed or composite ids); double-encoded ids like '%7C' left un-decoded; whitespace or trailing '/' fragments captured by an over-broad regex in custom preprocessing.

Understand the failure class

Related errors


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