jackwener/OpenCLI · error · CommandExecutionError

${label} returned a malformed API envelope

Error message

${label} returned a malformed API envelope

What it means

Juejin's REST API wraps results in an envelope `{ err_no, err_msg, data }`. juejinFetch validates that the parsed JSON is a non-array object containing an `err_no` property and throws this CommandExecutionError when the shape doesn't match. It indicates a contract violation: the endpoint replied, but not with the documented Juejin envelope.

Source

Thrown at clis/juejin/utils.js:120

        );
    }
    if (resp.status === 429) {
        throw new CommandExecutionError(
            `${label} returned HTTP 429 (rate limited)`,
            'Juejin throttles bursty traffic; wait a few seconds and retry.',
        );
    }
    if (!resp.ok) {
        throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);
    }
    let payload;
    try {
        payload = await resp.json();
    } catch (err) {
        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);
    }
    if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.hasOwn(payload, 'err_no')) {
        throw new CommandExecutionError(`${label} returned a malformed API envelope`);
    }
    if (payload.err_no !== 0) {
        throw new CommandExecutionError(`${label} returned err_no ${payload.err_no}: ${payload.err_msg ?? ''}`);
    }
    return payload;
}

export function readDataArray(payload, label) {
    if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.hasOwn(payload, 'data')) {
        throw new CommandExecutionError(`${label} returned a malformed payload`);
    }
    if (!Array.isArray(payload.data)) {
        throw new CommandExecutionError(`${label} returned a non-array data field`);
    }
    if (payload.data.length === 0) {
        throw new EmptyResultError(label, `${label} returned no articles.`);
    }
    return payload.data;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log the parsed body to see the actual shape and compare it with the expected `{ err_no, err_msg, data }` envelope.
  2. Update the adapter if Juejin renamed/removed `err_no` — check the calling command's endpoint against current Juejin API docs.
  3. Confirm you are hitting the intended path (a wrong path on the same host can return a different envelope with 200).
  4. Upgrade the opencli juejin adapter if a newer version adapts to the changed schema.
  5. Report/pin the endpoint: if a gateway is substituting its own JSON error, fix the proxy config instead.

Example fix

// before (adapter assumes envelope blindly)
const payload = await juejinFetch(path, body, label);

// after (caller detects envelope drift defensively)
const payload = await juejinFetch(path, body, label); // throws on bad envelope
if (!('data' in payload)) throw new Error(`unexpected envelope: ${JSON.stringify(payload).slice(0, 200)}`);
Defensive patterns

Strategy: type-guard

Validate before calling

null

Type guard

function isJuejinEnvelope(payload) {
  return (
    payload !== null &&
    typeof payload === 'object' &&
    !Array.isArray(payload) &&
    Object.hasOwn(payload, 'err_no') &&
    typeof payload.err_no === 'number'
  );
}

Try / catch

try {
  const payload = await juejinFetch(path, body, label); // envelope already validated internally
  if (!Array.isArray(payload.data)) throw new Error('envelope ok but data missing');
} catch (err) {
  if (err instanceof CommandExecutionError && /malformed API envelope/.test(err.message)) {
    console.error(`Juejin API contract changed or a gateway answered: ${err.message}. Update/inspect the adapter.`);
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: The response parsed as JSON but is null, an array, a scalar, or an object without `err_no` — e.g. the endpoint changed its schema, a gateway returned its own JSON error object ( `{message: ...}` ), or the request hit an unexpected Juejin route returning a different envelope.

Common situations: Juejin silently changing/redesigning an endpoint's response shape (adapter version drift); a CDN or gateway returning its own JSON `{error: ...}` with 200; hitting a path that belongs to a different Juejin API family with a different envelope; an old cached adapter against a new backend.

Understand the failure class

Related errors


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