{"record":{"id":"9bbbc89f8748c2a4","repo":"JuliusBrussee/caveman","slug":"field-is-not-an-array-got-typeof-value","errorCode":null,"errorMessage":"${field} is not an array (got ${typeof value})","messagePattern":"(.+?) is not an array \\(got (.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/subagent-tax/lib/analyze.mjs","lineNumber":20,"sourceCode":"// prints. All sizes are JSON-serialized character counts of what the harness\n// actually sent — nothing is inferred here except the chars themselves.\n\nconst LLM_KINDS = new Set([\n  \"anthropic-messages\",\n  \"openai-chat\",\n  \"openai-responses\",\n  \"gemini-generatecontent\",\n]);\n\nconst jsonChars = (value) => (value === undefined || value === null ? 0 : JSON.stringify(value).length);\n\nconst isSystemRole = (m) => m && (m.role === \"system\" || m.role === \"developer\");\n\n// A field present in an unexpected shape is reported as unparsed, never\n// silently counted as zero — a false \"0 tools\" reads as a real measurement.\nfunction expectArray(value, field) {\n  if (value === undefined || value === null) return [];\n  if (!Array.isArray(value)) throw new Error(`${field} is not an array (got ${typeof value})`);\n  return value;\n}\n\nfunction anthropicParts(body) {\n  const tools = expectArray(body.tools, \"tools\").map((t) => ({ name: t?.name ?? t?.type ?? \"?\", chars: jsonChars(t) }));\n  // The fixed instruction payload arrives as `system` AND, in some harnesses,\n  // as system-role entries inside `messages` — count both or the flagship row\n  // under-reports its own prefix.\n  const messages = expectArray(body.messages, \"messages\");\n  const sysMessages = messages.filter(isSystemRole);\n  const rest = messages.filter((m) => !isSystemRole(m));\n  return {\n    system_chars: jsonChars(body.system) + (sysMessages.length ? jsonChars(sysMessages) : 0),\n    tools,\n    messages_chars: jsonChars(rest.length ? rest : undefined),\n  };\n}\n","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/subagent-tax/lib/analyze.mjs#L2-L38","documentation":"expectArray() normalizes optional list fields from captured harness request bodies (e.g. body.tools) before analysis. The library treats missing/null as an empty array, but any value present in a non-array shape is thrown instead of silently coerced, because a mis-shaped field counted as zero would produce a false measurement (e.g. \"0 tools\"). The message names the field and the actual typeof value seen.","triggerScenarios":"Calling expectArray(value, field) — directly or via anthropicParts() — where value is defined and non-null but not an array: e.g. body.tools is an object map, a string, or a number from a malformed or hand-edited captured request body.","commonSituations":"Replaying a captured Claude Code / anthropic-messages request whose body was altered by a harness or proxy; hand-editing a fixture and replacing the tools array with a single tool object; a schema change in an upstream harness that emits tools as a keyed object instead of a list.","solutions":["Inspect the named field in the request body and correct it to be an array (wrap a single object in [ ... ] if one tool was supplied).","If the field is truly optional/absent, delete the key or set it to null — expectArray maps both to [].","Validate captured bodies before analysis with Array.isArray(body.tools) and fail fast at capture time.","If a provider/harness legitimately changed shape (object map), map values with Object.values() before passing to the analyzer."],"exampleFix":"// before\nanthropicParts({ tools: { web_search: { type: \"web_search\" } } })\n// after\nconst raw = { tools: { web_search: { type: \"web_search\" } } };\nanthropicParts({ tools: Array.isArray(raw.tools) ? raw.tools : Object.values(raw.tools) })","handlingStrategy":"validation","validationCode":"function assertArrayField(body, field) {\n  const v = body?.[field];\n  if (v === undefined || v === null) return [];\n  if (!Array.isArray(v)) throw new TypeError(`${field} must be an array, got ${typeof v}`);\n  return v;\n}\n// call before analysis:\nassertArrayField(requestBody, \"tools\");","typeGuard":"const isArrayField = (v) => v === undefined || v === null || Array.isArray(v);","tryCatchPattern":"try {\n  analyzeBody(body);\n} catch (e) {\n  if (/is not an array \\(got /.test(e.message)) {\n    console.error(`Malformed captured body: ${e.message}`); // skip or fix fixture\n  } else throw e;\n}","preventionTips":["Validate captured request bodies with a JSON schema at capture time, not analysis time.","Never hand-edit fixture arrays into bare objects; keep single-item values wrapped in arrays.","Store the raw body and apply shape fixes in a preprocessing step you can audit."],"tags":["type-mismatch","validation","request-body"],"backgroundTag":"type-mismatch","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}