tinyhumansai/openhuman · error · SpecError
must be an object
Error message
must be an object
What it means
Each element of spec.agents must be a plain object (not null, not an array, not a primitive). This is the first per-agent check inside the loop, tagged with the exact position path "agents[i]" so you can locate the bad entry. It mirrors the top-level object check applied to array elements.
Source
Thrown at scripts/agent-batch/lib.mjs:91
}
if (!Array.isArray(spec.agents) || spec.agents.length === 0) {
throw new SpecError("agents must be a non-empty array", "agents");
}
if (spec.agents.length > 25) {
throw new SpecError(
`batch size ${spec.agents.length} exceeds hard cap of 25`,
"agents",
);
}
const seenId = new Set();
const seenIssue = new Set();
const seenBranch = new Set();
for (let i = 0; i < spec.agents.length; i++) {
const agent = spec.agents[i];
const at = `agents[${i}]`;
if (!agent || typeof agent !== "object" || Array.isArray(agent)) {
throw new SpecError("must be an object", at);
}
for (const key of REQUIRED_AGENT) {
if (!(key in agent))
throw new SpecError(`missing required field "${key}"`, at);
}
if (typeof agent.id !== "string" || !/^a\d{2,3}$/.test(agent.id)) {
throw new SpecError(
`id must match /^a\\d{2,3}$/ (got "${agent.id}")`,
`${at}.id`,
);
}
if (seenId.has(agent.id))
throw new SpecError(`duplicate id "${agent.id}"`, `${at}.id`);
seenId.add(agent.id);
if (!Number.isInteger(agent.issue) || agent.issue <= 0) {
throw new SpecError("issue must be a positive integer", `${at}.issue`);
}
if (seenIssue.has(agent.issue)) {View on GitHub (pinned to a221052e0d)
Solutions
- Inspect spec.agents at the index given in the error path (e.g. agents[3]) and replace the null/primitive with a full agent object
- Filter nulls before validating if your generator can emit them: spec.agents = spec.agents.filter(Boolean)
- Add the five required agent fields (id, issue, title, branch, owned_paths) to the entry
Example fix
// before
"agents": [ { ...a01... }, null, { ...a02... } ]
// after
"agents": [ { ...a01... }, { "id": "a02", ... }, { ...a03... } ] Defensive patterns
Strategy: validation
Validate before calling
const bad = spec.agents.findIndex((a) => !a || typeof a !== "object" || Array.isArray(a));
if (bad !== -1) {
console.error(`agents[${bad}] must be an object`);
process.exit(1);
} Type guard
/** @param {unknown} v */
function isPlainObject(v) {
return typeof v === "object" && v !== null && !Array.isArray(v);
} Try / catch
try {
validateSpec(spec);
} catch (e) {
if (e instanceof SpecError) {
const m = /^agents\[(\d+)]: must be an object$/.exec(e.message);
if (m) console.error(`bad entry at index ${m[1]} — replace null/placeholder with a full agent object`);
process.exit(1);
}
throw e;
} Prevention
- Filter Boolean on generated agent lists before serializing
- Lint spec files for null entries in agents (a quick jq check: all(.agents[]; type == "object"))
When it happens
Trigger: An agents array containing null (e.g. trailing comma artifacts in generated JSON that parse to null via explicit null), a string like "a01", a number, or a nested array [[...]].
Common situations: Generator emitting null placeholders for skipped agents; hand-editing that leaves a stray comma or wraps an agent in an extra array; YAML-to-JSON conversion producing a list of scalars.
Related errors
- batch_id must be a kebab-case slug
- base_repo must be "tinyhumansai/openhuman" (got "${spec.base
- base_branch must be "main" (got "${spec.base_branch}")
- tracking_issue must be a positive integer
- agents must be a non-empty array
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/fdcf767169be5445.
Report an issue: GitHub.