{"record":{"id":"668318d4bd5a5cfa","repo":"jackwener/OpenCLI","slug":"fetch-error-668318","errorCode":"FETCH_ERROR","errorMessage":"ONES ${apiPath}: ${detail}","messagePattern":"ONES (.+?): (.+?)","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"error","filePath":"clis/ones/common.js","lineNumber":76,"sourceCode":"        if (parts.length)\n            return parts.filter(Boolean).join(' · ');\n    }\n    return status === 401 ? 'Unauthorized' : `HTTP ${status}`;\n}\n/** ONES 部分接口 HTTP 200 但 body 仍为错误（如 reason: ServerError） */\nfunction throwIfOnesPeekBusinessError(apiPath, parsed) {\n    if (parsed === null || typeof parsed !== 'object')\n        return;\n    const o = parsed;\n    if (Array.isArray(o.groups))\n        return;\n    const hasErr = (typeof o.reason === 'string' && o.reason.length > 0) ||\n        (typeof o.errcode === 'string' && o.errcode.length > 0) ||\n        (typeof o.type === 'string' && o.type.length > 0);\n    if (!hasErr)\n        return;\n    const detail = summarizeOnesError(200, parsed);\n    throw new CliError('FETCH_ERROR', `ONES ${apiPath}: ${detail}`, '若 query 不合法会返回 ServerError；可试 opencli ones tasks（空 must）或检查筛选器文档。响应全文可用 -v 或临时打日志。');\n}\nexport async function onesFetchInPageWithMeta(page, apiPath, options = {}) {\n    if (!options.skipGoto) {\n        await gotoOnesHome(page);\n    }\n    const url = onesApiUrl(apiPath);\n    const method = (options.method ?? 'GET').toUpperCase();\n    const auth = options.auth !== false;\n    const body = options.body ?? null;\n    const includeCt = body !== null || method === 'POST' || method === 'PUT' || method === 'PATCH';\n    const headers = buildHeaders(auth, includeCt);\n    const urlJs = JSON.stringify(url);\n    const methodJs = JSON.stringify(method);\n    const headersJs = JSON.stringify(headers);\n    const bodyJs = body === null ? 'null' : JSON.stringify(body);\n    const raw = await page.evaluate(`\n    (async () => {\n      const url = ${urlJs};","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ones/common.js#L58-L94","documentation":"throwIfOnesPeekBusinessError checks filters/peek responses that returned HTTP 200 but whose body is still an error object. If the parsed JSON has a non-empty `reason`, `errcode`, or `type` field (and no `groups` array), the CLI throws FETCH_ERROR with the ONES apiPath and a summarized error detail. This catches ONES ServerError bodies that hide behind a 200 status, typically caused by an invalid filter query.","triggerScenarios":"Calling `onesFetchInPage(page, 'project/api/project/filters/peek', ...)` (or a command like tasks/my-tasks that hits filters/peek) where the response is HTTP 200 but the JSON body contains e.g. {reason: 'ServerError'} instead of {groups: [...]}. Happens when the peek query JSON is malformed or references invalid field/filter values.","commonSituations":"A hand-built filter query with an invalid `must` clause; an ONES server build whose peek endpoint rejects the query shape the CLI sends; a changed/undocumented filter field uuid; copying a query from docs that the deployment's API version does not accept.","solutions":["Run the simpler command `opencli ones tasks` (empty must clause) to confirm the endpoint and auth work at all.","Re-run with -v (verbose) to dump the full response body and inspect the `reason`/`type` detail.","Validate your filter query: check field uuids and filter syntax against the ONES filters/peek documentation for your deployment version.","Simplify the query: remove optional must clauses and add them back one at a time to find the offending filter."],"exampleFix":"// before: complex peek query\nconst body = { must: [{ field: 'field009', op: 'in', values: ['unknown-uuid'] }] };\n// after: start minimal, then extend\nconst body = {}; // empty must; add validated filters incrementally","handlingStrategy":"type-guard","validationCode":"function peekBodyLooksLikeError(parsed) {\n  if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return false;\n  const o = parsed;\n  if (Array.isArray(o.groups)) return false;\n  return ['reason', 'errcode', 'type'].some(k => typeof o[k] === 'string' && o[k].length > 0);\n}\n// call only after HTTP ok, and surface parsed.reason before calling flattenPeekGroups","typeGuard":"function isPeekGroupsResponse(p) {\n  return p !== null && typeof p === 'object' && Array.isArray(p.groups);\n}","tryCatchPattern":"try {\n  const rows = await opencli.ones.tasks();\n} catch (e) {\n  if (e.code === 'FETCH_ERROR' && e.message.includes('filters/peek')) {\n    console.error('Peek query rejected:', e.message, '\\nHint:', e.hint);\n    // fall back to minimal query\n  } else throw e;\n}","preventionTips":["Start with an empty must clause and add filters incrementally.","Validate field uuids against your deployment's filter field list before building queries.","Run with -v during development to capture the full peek response."],"tags":["ones","api","http-200-with-error-body","filter-query"],"backgroundTag":"http-200-business-error","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}