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

  1. 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
  2. Filter nulls before validating if your generator can emit them: spec.agents = spec.agents.filter(Boolean)
  3. 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

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


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/fdcf767169be5445. Report an issue: GitHub.