{"record":{"id":"0f24573171adbcf8","repo":"jackwener/OpenCLI","slug":"http-error-0f2457","errorCode":"HTTP_ERROR","errorMessage":"`northbound failed: HTTP ${resp.status}`","messagePattern":"`northbound failed: HTTP (.+?)`","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"error","filePath":"clis/eastmoney/northbound.js","lineNumber":36,"sourceCode":"  args: [\n    { name: 'direction', type: 'string', default: 'north', help: '方向：north (北向，即外资买A) / south (南向，即内地买港)' },\n    { name: 'limit',     type: 'int',    default: 10,      help: '返回最近 N 分钟' },\n  ],\n  columns: ['time', 'cumulativeNetYi', 'minuteNetYi', 'totalNetYi'],\n  func: async (args) => {\n    const dir = String(args.direction ?? 'north').toLowerCase();\n    if (!['north', 'south', 'n', 's'].includes(dir)) {\n      throw new CliError('INVALID_ARGUMENT', `Unknown direction \"${dir}\". Valid: north / south`);\n    }\n    const limit = Math.max(1, Math.min(Number(args.limit) || 10, 240));\n\n    const url = new URL('https://push2.eastmoney.com/api/qt/kamtbs.rtmin/get');\n    url.searchParams.set('fields1', 'f1,f2,f3,f4');\n    url.searchParams.set('fields2', 'f51,f52,f54,f56');\n    url.searchParams.set('ut', 'b2884a393a59ad64002292a3e90d46a5');\n\n    const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });\n    if (!resp.ok) throw new CliError('HTTP_ERROR', `northbound failed: HTTP ${resp.status}`);\n    const data = await resp.json();\n    const key = (dir === 'south' || dir === 's') ? 's2n' : 'n2s';\n    /** @type {string[]} */\n    const rows = Array.isArray(data?.data?.[key]) ? data.data[key] : [];\n    if (rows.length === 0) throw new CliError('NO_DATA', `No ${key} data returned`);\n\n    // CSV fields per entry: \"HH:MM,cumulative_net(万), minute_net(万), total_net(万)\"\n    // Drop rows with '-' (after market close or before open). Convert 万元 → 亿元 for readability.\n    const valid = rows\n      .map((r) => r.split(','))\n      .filter((c) => c.length >= 4 && c[1] !== '-');\n    if (valid.length === 0) {\n      throw new CliError('NO_DATA', `${key} has no valid minute data yet (markets may not be open)`);\n    }\n    return valid.slice(-limit).map(([time, cum, min, total]) => ({\n      time,\n      cumulativeNetYi: +(Number(cum) / 10000).toFixed(4),\n      minuteNetYi: +(Number(min) / 10000).toFixed(4),","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/eastmoney/northbound.js#L18-L54","documentation":"After fetching https://push2.eastmoney.com/api/qt/kamtbs.rtmin/get, northbound.js checks resp.ok and throws this CliError with code HTTP_ERROR if the response status is not 2xx. It wraps the upstream HTTP status in the message so the developer knows the eastmoney endpoint rejected or failed the request.","triggerScenarios":"Any non-2xx response from push2.eastmoney.com when calling the northbound command — e.g. 403 from bot detection / rate limiting, 404 if the endpoint path changes, 5xx from eastmoney server-side issues, or a captive proxy returning an error page status.","commonSituations":"Eastmoney blocking datacenter IPs or aggressively rate-limiting repeated polling; running from a region or network where the request is intercepted; eastmoney temporarily changing/removing the API path or the 'ut' token becoming invalid; transient 5xx during high-traffic market hours.","solutions":["Re-run the command after a short wait — transient 5xx and rate limits usually clear","Check the HTTP status in the message: 403 suggests blocking/rate-limiting, 5xx suggests eastmoney-side trouble","Slow down polling frequency and add backoff between calls","Verify network/proxy access to push2.eastmoney.com (curl -I the URL)","Check whether eastmoney changed the API path or ut token and update the URL in clis/eastmoney/northbound.js"],"exampleFix":"// before\nconst resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });\n// after — retry with backoff on transient failures\nconst resp = await fetchWithRetry(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, { retries: 3 });","handlingStrategy":"retry","validationCode":"// Preflight connectivity check\nconst probe = await fetch('https://push2.eastmoney.com/api/qt/kamtbs.rtmin/get?fields1=f1&fields2=f51&ut=b2884a393a59ad64002292a3e90d46a5', { headers: { 'User-Agent': 'Mozilla/5.0' } });\nif (!probe.ok) throw new Error(`eastmoney unreachable: HTTP ${probe.status}`);","typeGuard":"null","tryCatchPattern":"try {\n  await runNorthbound(args);\n} catch (e) {\n  if (e.code === 'HTTP_ERROR' && /HTTP (5\\d\\d|429)/.test(e.message)) {\n    await sleep(backoff); return runNorthbound(args); // retry transient failures\n  }\n  if (e.code === 'HTTP_ERROR' && /HTTP 403/.test(e.message)) console.error('Blocked/rate-limited by eastmoney; reduce frequency or change network.');\n  else throw e;\n}","preventionTips":["Add exponential backoff with jitter around fetch calls","Poll at modest intervals (eastmoney rate-limits aggressive scraping)","Monitor the specific status code: 403 = blocking, 5xx = server-side, 429 = rate limit","Pin a realistic browser User-Agent and avoid datacenter IPs when possible","Alert on persistent 403/404 which may mean the API path or ut token changed"],"tags":["network","http","upstream-api","eastmoney"],"backgroundTag":"http-error-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}