jackwener/OpenCLI · error · CommandExecutionError

juejin recommend returned a malformed cursor

Error message

juejin recommend returned a malformed cursor

What it means

The Juejin recommend CLI wraps its pagination cursor parser (requireCursor) in a try/catch inside readResponseCursor. When the API response contains a cursor value that cannot be parsed as a valid non-negative decimal integer cursor, the underlying ArgumentError is replaced with a CommandExecutionError indicating the remote service returned malformed data. This signals an unexpected API response shape rather than a caller mistake.

Source

Thrown at clis/juejin/recommend.js:21

// Hits the `recommend_all_feed` endpoint, which mirrors what the Juejin web UI
// renders on the front page; `sort_type` 200 is the default "recommended" mix.
import { cli, Strategy } from '@jackwener/opencli/registry';
import { CommandExecutionError } from '@jackwener/opencli/errors';
import {
    juejinFetch,
    mapFeedItem,
    readDataArray,
    requireBoundedInt,
    requireCursor,
} from './utils.js';

function readResponseCursor(value) {
    if (value == null || value === '') return '';
    try {
        return requireCursor(value);
    }
    catch {
        throw new CommandExecutionError('juejin recommend returned a malformed cursor');
    }
}

cli({
    site: 'juejin',
    name: 'recommend',
    access: 'read',
    description: 'Juejin (掘金) homepage recommended article feed',
    domain: 'api.juejin.cn',
    strategy: Strategy.PUBLIC,
    browser: false,
    args: [
        { name: 'limit', type: 'int', default: 20, help: 'Max articles (1-100, single page).' },
        { name: 'cursor', type: 'string', default: '0', help: 'Pagination cursor; pass back the previous response\'s next-page cursor to keep scrolling.' },
    ],
    columns: ['rank', 'article_id', 'title', 'brief', 'views', 'likes', 'comments', 'author', 'tags', 'url', 'next_cursor', 'has_more'],
    func: async (args) => {
        const limit = requireBoundedInt(args.limit, 20, 100);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry the command once — transient CDN/proxy corruption can produce a bad payload; a fresh request often returns a valid cursor.
  2. Check whether the juejin API changed its cursor format; update the CLI's requireCursor regex/parser to accept the new format.
  3. Pass an explicit --cursor argument with a known-good numeric cursor from a previous successful response to bypass the malformed server cursor.
  4. Update the CLI package if a newer version adapts to the new API response shape.
  5. Report/inspect the raw payload (log payload.cursor) to confirm what the server actually returned before filing a bug.

Example fix

// before (strict numeric-only cursor parsing)
if (typeof raw === 'string' && /^(0|[1-9]\d*)$/.test(raw)) return raw;
// after (accept opaque server cursors)
if (typeof raw === 'string' && /^[A-Za-z0-9_-]+$/.test(raw)) return raw;
Defensive patterns

Strategy: try-catch

Validate before calling

function isPlausibleCursor(v){ return v == null || v === '' || (typeof v === 'string' && /^[A-Za-z0-9]+$/.test(v)) || (typeof v === 'number' && Number.isSafeInteger(v) && v >= 0); }

Type guard

function isUsableCursor(v){ return (typeof v === 'string' && /^(0|[1-9]\d*)$/.test(v)) || (typeof v === 'number' && Number.isSafeInteger(v) && v >= 0) || v == null || v === ''; }

Try / catch

try {
  const page = cliRecommend({ limit: 20, cursor });
} catch (e) {
  if (/malformed cursor/.test(e.message)) {
    cursor = '0'; // restart pagination from the beginning
    const page = cliRecommend({ limit: 20, cursor });
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the juejin recommend command when the upstream API returns a payload whose cursor field is a non-empty value that fails requireCursor validation, e.g. a string with non-digit characters, a negative number, a float, an object, or a boolean.

Common situations: Juejin changes its cursor format (e.g. switches to opaque base64 tokens or signed cursors); a proxy or cache returns an HTML error page parsed as JSON with a string cursor; scraping/CDN interposition alters response fields; a mocked or recorded fixture uses a placeholder cursor value like '<cursor>' or 'abc'.

Understand the failure class

Related errors


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