jackwener/OpenCLI · error · Error
extraction_drift
extraction_drift
Error message
[taxonomy=extraction_drift] site=${site} command=search all rows rejected by quality gate (raw=${raw count}) What it means
toProcurementSearchRecords applies a per-site quality gate (qualityRejectReason) to each raw search row. If raw rows exist but every one is rejected, the library assumes the site's markup changed and parsers are extracting wrong data, so it throws a taxonomy error with code 'extraction_drift' rather than returning an empty-but-misleading result set.
Source
Thrown at clis/jianyu/shared/procurement-contract.js:291
return deduped;
}
export function formatTaxonomyError(taxonomy, { site, command, detail, }) {
return `[taxonomy=${taxonomy}] site=${site} command=${command} ${cleanText(detail)}`;
}
export function taxonomyError(taxonomy, context) {
return new Error(formatTaxonomyError(taxonomy, context));
}
export function toProcurementSearchRecords(rows, { site, query, limit, }) {
const normalizedRows = dedupeByTitleUrl(rows.map((row) => normalizeCoreRecord(row, { sourceSite: site })));
const accepted = [];
for (const row of normalizedRows) {
const rejectReason = qualityRejectReason(row, query);
if (rejectReason)
continue;
accepted.push(row);
}
if (normalizedRows.length > 0 && accepted.length === 0) {
throw taxonomyError('extraction_drift', {
site,
command: 'search',
detail: `all rows rejected by quality gate (raw=${normalizedRows.length})`,
});
}
return accepted
.slice(0, Math.max(1, limit))
.map((row, index) => ({
rank: index + 1,
...row,
}));
}
export function toProcurementDetailRecord({ title, url, contextText, publishTime, }, { site, query = '', }) {
const core = normalizeCoreRecord({
title,
url,
date: publishTime,
contextText,View on GitHub (pinned to 49907e53dc)
Solutions
- Dump the raw rows and check why each fails qualityRejectReason, then fix the parser/normalizer
- Update selectors/regexes for the drifted site layout
- Relax the quality gate only if the rows are genuinely valid
- Pin/upgrade the library version that supports the current site markup
Defensive patterns
Strategy: validation
Validate before calling
function rowsLookSane(rows) {
return rows.length === 0 || rows.some(r => r.title && r.title.length > 3 && r.url);
}
if (!rowsLookSane(rawRows)) console.warn('quality gate may reject all rows for site', site); Type guard
function isExtractionDrift(e) {
return e instanceof Error && /\[taxonomy=extraction_drift\]/.test(e.message);
} Try / catch
try {
rows = await procurementSearch(page, { site, query });
} catch (e) {
if (isExtractionDrift(e)) {
// all rows rejected: flag site for parser review, do not treat as empty results
reportDrift(site, 'search');
return [];
}
throw e;
} Prevention
- Log raw rows alongside quality-gate rejections to debug drift fast
- Alert on extraction_drift per site — it signals markup changes
- Keep parser selectors per-site and versioned
- Sample real pages in CI to catch drift before production
When it happens
Trigger: Calling the jianyu procurement 'search' command for a site where the parser returns rows but none pass qualityRejectReason (e.g. missing/malformed titles, wrong fields, filtered-out noise rows).
Common situations: Site redesign or markup changes breaking field extraction; quality-gate thresholds tightened while the site's data format shifted; scraping a site variant whose layout differs from the supported template; proxy/mirror serving stale or rewritten HTML.
Related errors
- Ctrip round-trip flight rows were missing required airline/f
- LinkedIn sent invitations contained a malformed invitation c
- extraction_drift
- Not a git repository
- Working tree not clean: ${status}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/368f8d00e752de18.
Report an issue: GitHub.