JuliusBrussee/caveman · error · Error
is not an array (got )
Error message
${field} is not an array (got ${typeof value}) What it means
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.
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.
Example fix
// before
anthropicParts({ tools: { web_search: { type: "web_search" } } })
// after
const raw = { tools: { web_search: { type: "web_search" } } };
anthropicParts({ tools: Array.isArray(raw.tools) ? raw.tools : Object.values(raw.tools) }) Defensive patterns
Strategy: validation
Validate before calling
function assertArrayField(body, field) {
const v = body?.[field];
if (v === undefined || v === null) return [];
if (!Array.isArray(v)) throw new TypeError(`${field} must be an array, got ${typeof v}`);
return v;
}
// call before analysis:
assertArrayField(requestBody, "tools"); Type guard
const isArrayField = (v) => v === undefined || v === null || Array.isArray(v);
Try / catch
try {
analyzeBody(body);
} catch (e) {
if (/is not an array \(got /.test(e.message)) {
console.error(`Malformed captured body: ${e.message}`); // skip or fix fixture
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- cursor. must be a string
- cursor must be an object
- Invalid caveman_branch_id
- Invalid caveman_cache_epoch
- must be a string
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/9bbbc89f8748c2a4.
Report an issue: GitHub.
Appendix: source
Thrown at packages/subagent-tax/lib/analyze.mjs:20
// prints. All sizes are JSON-serialized character counts of what the harness
// actually sent — nothing is inferred here except the chars themselves.
const LLM_KINDS = new Set([
"anthropic-messages",
"openai-chat",
"openai-responses",
"gemini-generatecontent",
]);
const jsonChars = (value) => (value === undefined || value === null ? 0 : JSON.stringify(value).length);
const isSystemRole = (m) => m && (m.role === "system" || m.role === "developer");
// A field present in an unexpected shape is reported as unparsed, never
// silently counted as zero — a false "0 tools" reads as a real measurement.
function expectArray(value, field) {
if (value === undefined || value === null) return [];
if (!Array.isArray(value)) throw new Error(`${field} is not an array (got ${typeof value})`);
return value;
}
function anthropicParts(body) {
const tools = expectArray(body.tools, "tools").map((t) => ({ name: t?.name ?? t?.type ?? "?", chars: jsonChars(t) }));
// The fixed instruction payload arrives as `system` AND, in some harnesses,
// as system-role entries inside `messages` — count both or the flagship row
// under-reports its own prefix.
const messages = expectArray(body.messages, "messages");
const sysMessages = messages.filter(isSystemRole);
const rest = messages.filter((m) => !isSystemRole(m));
return {
system_chars: jsonChars(body.system) + (sysMessages.length ? jsonChars(sysMessages) : 0),
tools,
messages_chars: jsonChars(rest.length ? rest : undefined),
};
}
View on GitHub (pinned to 3ee70a1026)