paperclipai/paperclip · error · Error
Expected a JSON object: ${path}
Error message
Expected a JSON object: ${path} What it means
loadObject parses a JSON file and requires the top-level value to be a plain object (not null, not an array, not a primitive). It is used to load campaign and roster files for the runner protocol eval campaign. Any JSON document whose root is not an object is rejected with the offending path in the message.
Source
Thrown at packages/paperclip-runner/scripts/runner-protocol-eval-campaign.mjs:38
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/;
const ATTEMPT_FILES = new Set([
"request.json",
"artifact.json",
"score.json",
"compiled-fixture.json",
"case.json",
"config.json",
]);
function json(value) {
return `${JSON.stringify(value, null, 2)}\n`;
}
async function loadObject(path) {
const value = JSON.parse(await readFile(path, "utf8"));
if (value === null || Array.isArray(value) || typeof value !== "object") {
throw new Error(`Expected a JSON object: ${path}`);
}
return value;
}
function safeId(value, label = "identifier") {
const result = String(value ?? "");
if (!SAFE_ID.test(result)) throw new Error(`Unsafe ${label}: ${result}`);
return result;
}
function inside(root, candidate, label) {
const rel = relative(resolve(root), resolve(candidate));
if (!rel || rel === ".." || rel.startsWith(`..${sep}`)) {
throw new Error(`${label} escapes its declared root`);
}
return resolve(candidate);
}
View on GitHub (pinned to 01ad858492)
Solutions
- Open the file at the path in the message and wrap the top-level value in an object with the expected fields (schema, id, cases/lanes).
- Verify you are pointing at the intended file, not a list-valued JSON document.
- If the data is genuinely a list, iterate and call loadObject per item on individual files instead.
Example fix
// before: rosters.json contains
// [ { "id": "r1", ... } ]
// after: each roster file contains
// { "schema": "paperclip-runner/live-roster/v1", "id": "r1", "cases": [ ... ] } Defensive patterns
Strategy: validation
Validate before calling
const value = JSON.parse(await readFile(path, "utf8"));
if (value === null || Array.isArray(value) || typeof value !== "object") {
throw new Error(`Expected a JSON object: ${path}`);
} Type guard
const isJsonObject = (v) => v !== null && !Array.isArray(v) && typeof v === "object";
Try / catch
try {
campaign = await loadObject(path);
} catch (err) {
if (err.message.startsWith("Expected a JSON object")) console.error(`Fix the top-level shape of ${path}: must be { ... }, not an array/primitive.`);
throw err;
} Prevention
- Validate campaign/roster JSON with a schema validator (e.g. zod) at load time.
- Never hand-edit JSON root shape; use object wrappers with explicit schema fields.
- Add a JSON lint/CI check that asserts every campaign/roster file root is an object.
When it happens
Trigger: Calling loadObject(path) on a file whose JSON.parse result is null, an array, a string, a number, or a boolean — e.g. a roster file starting with '[' or a file containing just 'null'.
Common situations: A roster/campaign JSON was hand-edited into an array form; an export tool wrote a JSON list; a file contains only 'null'; wrong file passed (e.g. a JSON list of rosters instead of a single roster object).
Related errors
- Expected a JSON object: ${path}
- Public report campaign metadata is invalid
- Chat SDK ${kind} state is not JSON-serializable
- Invalid status '${String(rawStatus)}'. Must be one of: ${PLU
- "tool" is required and must be a string
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/a9db17755b1e959a.
Report an issue: GitHub.