jackwener/OpenCLI · error · CliError
INVALID_ARGUMENT
INVALID_ARGUMENT
Error message
${err instanceof Error ? err.message : err} What it means
CliError('INVALID_ARGUMENT') thrown in clis/eastmoney/holders.js when toSecucode(args.symbol) rejects the user-supplied symbol. The CLI wraps the plain Error into a typed CliError so callers can distinguish bad input from network failures. This is the holders command's input-validation gate before any HTTP request is made.
Source
Thrown at clis/eastmoney/holders.js:45
cli({
site: 'eastmoney',
name: 'holders',
access: 'read',
description: '十大流通股东(A股 F10 数据)',
domain: 'datacenter-web.eastmoney.com',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'symbol', required: true, positional: true, help: 'A股代码(600519 / sh600519 等)' },
{ name: 'limit', type: 'int', default: 10, help: '返回股东数(默认十大流通股东)' },
],
columns: ['rank', 'reportDate', 'name', 'holdNum', 'floatRatio', 'change'],
func: async (args) => {
/** @type {string} */
let secucode;
try { secucode = toSecucode(args.symbol); }
catch (err) { throw new CliError('INVALID_ARGUMENT', `${err instanceof Error ? err.message : err}`); }
const limit = Math.max(1, Math.min(Number(args.limit) || 10, 50));
const url = new URL('https://datacenter-web.eastmoney.com/api/data/v1/get');
url.searchParams.set('sortColumns', 'END_DATE,HOLDER_RANK');
url.searchParams.set('sortTypes', '-1,1');
url.searchParams.set('pageSize', String(Math.max(limit, 10)));
url.searchParams.set('pageNumber', '1');
url.searchParams.set('reportName', 'RPT_F10_EH_FREEHOLDERS');
url.searchParams.set('columns', 'SECUCODE,SECURITY_CODE,END_DATE,HOLDER_RANK,HOLDER_NAME,HOLD_NUM,FREE_HOLDNUM_RATIO,HOLD_NUM_CHANGE');
url.searchParams.set('source', 'HSF10');
url.searchParams.set('client', 'PC');
url.searchParams.set('filter', `(SECUCODE="${secucode}")`);
const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
if (!resp.ok) throw new CliError('HTTP_ERROR', `holders failed: HTTP ${resp.status}`);
const data = await resp.json();
const rows = Array.isArray(data?.result?.data) ? data.result.data : [];
if (rows.length === 0) throw new CliError('NO_DATA', `No shareholder data for ${secucode}`);View on GitHub (pinned to 49907e53dc)
Solutions
- Supply a 6-digit A-share code (e.g. 600519) or prefixed form (1.600519 / 0.000001).
- Normalize the symbol first: trim, strip 'sh'/'sz'/'bj' prefixes, convert full-width digits.
- Validate with /((^\d\.)|^)\d{6}$/ before invoking the CLI to fail early with your own message.
- Branch on CliError code 'INVALID_ARGUMENT' in your wrapper to print usage help instead of a stack trace.
- If you need textual prefixes or HK/US symbols, extend toSecucode — the shipped version doesn't accept them.
Example fix
// caller-side validation before the CLI/library call
// before
const holders = await fetchHolders({ symbol: args.symbol });
// after
const symbol = String(args.symbol ?? '').trim().replace(/^(sh|sz|bj)/i, '');
if (!/^\d{6}$/.test(symbol)) throw new CliError('INVALID_ARGUMENT', `--symbol must be a 6-digit A-share code, got: ${args.symbol}`);
const holders = await fetchHolders({ symbol }); Defensive patterns
Strategy: validation
Validate before calling
const symbol = String(args.symbol ?? '').trim().replace(/^(sh|sz|bj)/i, '');
if (!/^\d{6}$/.test(symbol)) {
throw new CliError('INVALID_ARGUMENT', `--symbol must be a 6-digit A-share code, got: ${args.symbol}`);
} Type guard
/**
* @param {unknown} v
* @returns {v is string}
*/
function isParseableSymbol(v) {
if (typeof v !== 'string') return false;
const s = v.trim();
return /^\d+\.\d{6}$/.test(s) || /^\d{6}$/.test(s);
} Try / catch
try {
const holders = await fetchHolders({ symbol: '600519', limit: 10 });
} catch (err) {
if (err instanceof CliError && err.code === 'INVALID_ARGUMENT') {
console.error(`Bad --symbol: ${err.message}. Example: holders --symbol 600519`);
return;
}
throw err;
} Prevention
- Validate symbols with a regex at your CLI boundary before invoking the library.
- Normalize input: trim, strip exchange prefixes, reject empty strings.
- Show a usage example (e.g. holders --symbol 600519) whenever INVALID_ARGUMENT fires.
- Branch on CliError.code === 'INVALID_ARGUMENT' to separate input bugs from network faults.
- Reject non-A-share tickers early with a clear message instead of relying on the library error.
When it happens
Trigger: Invoking the holders CLI with a --symbol that toSecucode cannot normalize (non-numeric tickers, wrong digit counts, unsupported prefixes) — caught at `catch (err) { throw new CliError('INVALID_ARGUMENT', ...)` at clis/eastmoney/holders.js:45.
Common situations: Passing 'AAPL' or '00700' (HK) instead of a mainland A-share code, using 'sh600519' textual prefixes, or a symbol with hidden whitespace/full-width digits from copy-paste.
Related errors
- Unrecognized A-share symbol: ${input}
- INVALID_ARGUMENT
- INVALID_ARGUMENT
- eastmoney convertible returned a malformed response envelope
- eastmoney convertible returned malformed row at rank ${rank}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/af51f6dbfd146e46.
Report an issue: GitHub.