jackwener/OpenCLI · error · CommandExecutionError

eastmoney convertible returned malformed row at rank ${rank}

Error message

eastmoney convertible returned malformed row at rank ${rank}

What it means

Thrown by mapConvertibleRow (clis/eastmoney/convertible.js:85) when a single `diff` element is not a plain object (null, primitive, or array). Each diff item is expected to be an object keyed by eastmoney field codes (f12, f14, f232, ...). The error includes the 1-based rank so the offending row can be located. It is a CommandExecutionError from the row-mapping layer, meaning the envelope passed but a row inside is corrupt.

Source

Thrown at clis/eastmoney/convertible.js:85

  if (!Array.isArray(diff)) {
    throw new CommandExecutionError('eastmoney convertible returned malformed diff data');
  }
  if (diff.length === 0) {
    throw new EmptyResultError('eastmoney convertible');
  }
  return diff;
}

// Map a raw eastmoney clist `diff` item to an output row.
//
// #2109: f238 / f239 were previously emitted as `remainingYears` / `ytm`, but
// cross-verification (12/12 fingerprint hits) shows f239 is the putback trigger
// price (= convPrice × 0.7) and f238 is the pure-bond premium %. Real YTM /
// remaining term are not in this response's `fields`; adding the correct f-codes
// is a follow-up that needs a live push2 field dump cross-checked against jisilu.
export function mapConvertibleRow(it, rank) {
  if (!it || typeof it !== 'object' || Array.isArray(it)) {
    throw new CommandExecutionError(`eastmoney convertible returned malformed row at rank ${rank}`);
  }
  const bondCode = normalizeEastmoneyIdentityString(it.f12, 'f12', '');
  const bondName = normalizeEastmoneyIdentityString(it.f14, 'f14', bondCode);
  const stockCode = normalizeEastmoneyIdentityString(it.f232, 'f232', bondCode);
  const stockName = normalizeEastmoneyIdentityString(it.f234, 'f234', bondCode);
  for (const field of NUMERIC_FIELDS) {
    normalizeEastmoneyNumeric(it[field], field, bondCode);
  }
  return {
    rank,
    bondCode,
    bondName,
    bondPrice: normalizeEastmoneyNumeric(it.f2, 'f2', bondCode),
    bondChangePct: normalizeEastmoneyNumeric(it.f3, 'f3', bondCode),
    stockCode,
    stockName,
    stockPrice: normalizeEastmoneyNumeric(it.f229, 'f229', bondCode),
    stockChangePct: normalizeEastmoneyNumeric(it.f230, 'f230', bondCode),

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Filter non-object entries before mapping: `diff.filter(it => it && typeof it === 'object' && !Array.isArray(it))`.
  2. Inspect the raw response at the reported rank to see what eastmoney actually returned.
  3. Update test fixtures to contain only plain-object rows.
  4. Re-fetch — a transiently corrupt payload usually differs on retry.
  5. If eastmoney systematically emits null rows, add a skip-with-warning instead of throwing.

Example fix

// before
return capped.map((it, i) => mapConvertibleRow(it, i + 1));
// after
return capped
  .filter(it => it && typeof it === 'object' && !Array.isArray(it))
  .map((it, i) => mapConvertibleRow(it, i + 1));
Defensive patterns

Strategy: validation

Validate before calling

const cleanRows = diff.filter(it => it && typeof it === 'object' && !Array.isArray(it));
const rows = mapConvertibleRows(cleanRows, limit);

Type guard

function isConvertibleRow(it) {
  return it !== null && typeof it === 'object' && !Array.isArray(it) &&
    typeof it.f12 === 'string' && it.f12.trim() !== '';
}

Try / catch

try {
  return mapConvertibleRows(diff, limit);
} catch (e) {
  const m = String(e.message).match(/malformed row at rank (\d+)/);
  if (m) {
    console.warn(`skipping corrupt row at rank ${m[1]}`);
    return mapConvertibleRows(diff.filter(isConvertibleRow), limit);
  }
  throw e;
}

Prevention

When it happens

Trigger: mapConvertibleRows slices the diff array and maps each item; any element that is null or non-object triggers this with `rank = index + 1`. Happens when eastmoney pads diff with nulls, when fixtures are malformed, or when a JSON transform converts rows unexpectedly.

Common situations: Eastmoney returning sparse/null placeholder rows for suspended bonds; test fixtures containing nulls; response post-processing (e.g. CSV-ish serializers) flattening rows; calling mapConvertibleRow directly with wrong arguments in unit tests.

Understand the failure class

Related errors


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