paperclipai/paperclip · error · Error
Public report campaign metadata is invalid
Error message
Public report campaign metadata is invalid
What it means
validatePublicProtocolEvalReport requires campaign.json to declare schema "paperclip.runner-protocol-eval.campaign/v1" and a campaignId matching SAFE_CAMPAIGN (gha-<run>-<attempt> optionally suffixed with -report-<slug>). If either check fails the report metadata cannot be trusted for immutable public publication, so the publisher refuses it. This protects the S3 history from arbitrary or forged campaign identifiers.
Solutions
- Open <reportRoot>/campaign.json and confirm schema === "paperclip.runner-protocol-eval.campaign/v1"
- Set campaignId to the GHA form gha-<run_number>-<attempt> (optionally -report-<slug>, slug lowercase alnum/hyphen, max 40 chars)
- Regenerate the report with the current eval pipeline so it writes valid campaign metadata
- If testing locally, use a compliant synthetic ID like gha-1-1-test
Example fix
// before (campaign.json)
{ "schema": "campaign/v2", "campaignId": "local_run_42" }
// after
{ "schema": "paperclip.runner-protocol-eval.campaign/v1", "campaignId": "gha-42-1" } Defensive patterns
Strategy: validation
Validate before calling
const campaign = JSON.parse(fs.readFileSync(path.join(reportRoot, "campaign.json"), "utf8"));
const SAFE = /^gha-[1-9][0-9]*-[1-9][0-9]*(-report-[a-z0-9][a-z0-9-]{0,39})?$/;
if (campaign.schema !== "paperclip.runner-protocol-eval.campaign/v1" || !SAFE.test(String(campaign.campaignId)))
throw new Error("invalid campaign.json before publish"); Type guard
const isValidCampaign = (c) =>
!!c && typeof c === "object" && !Array.isArray(c) &&
c.schema === "paperclip.runner-protocol-eval.campaign/v1" &&
/^gha-[1-9][0-9]*-[1-9][0-9]*(-report-[a-z0-9][a-z0-9-]{0,39})?$/.test(String(c.campaignId ?? "")); Try / catch
try {
await publishProtocolEvalHistory({ reportRoot, destination, viewerRoot });
} catch (e) {
if (String(e.message).includes("campaign metadata is invalid")) {
console.error("check campaign.json schema/campaignId:", fs.readFileSync(path.join(reportRoot, "campaign.json"), "utf8"));
}
throw e;
} Prevention
- Always generate reports via the current eval pipeline, never hand-edit campaign.json
- Validate campaign.json in CI before the publish step
- Keep campaign IDs in the gha-<run>-<attempt> form produced by GitHub Actions
When it happens
Trigger: campaign.json missing the schema field or carrying a wrong/older schema value; campaignId absent, empty, or not matching the gha-N-N(-report-slug) pattern (e.g. a local name like 'my-eval-run' or containing uppercase/underscores); campaign.json being a JSON array or scalar (loadObject throws a different error, but a malformed object shape lands here).
Common situations: Running the publish script on a report directory produced by an outdated generator; hand-crafting a campaign.json for testing; CI environment producing a campaign ID that does not follow the GHA run/attempt convention.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- does not carry submitted form data
- : invalid AppDefinition
- Chat SDK state is not JSON-serializable
- Durable PRP run attachment template is invalid.
- Expected a JSON object
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/c1cf36bfe21dfcd5.
Report an issue: GitHub.
Appendix: source
Thrown at packages/paperclip-runner/scripts/publish-runner-protocol-eval-history.mjs:237
);
}
}
}
if (viewer) {
for (const file of viewer.files.keys())
if (!files.includes(file))
throw new Error(`Missing public viewer asset: ${file}`);
if (files.some((file) => /^attempts\/[^/]+\.html$/.test(file)))
throw new Error(
"Chat Evalbook must not mix in legacy plain attempt pages",
);
}
const campaign = await loadObject(join(root, "campaign.json"));
if (
campaign.schema !== "paperclip.runner-protocol-eval.campaign/v1" ||
!SAFE_CAMPAIGN.test(String(campaign.campaignId ?? ""))
) {
throw new Error("Public report campaign metadata is invalid");
}
return { files, campaign };
}
export async function createProtocolEvalBundleManifest(
reportRoot,
campaignId,
{ viewerRoot } = {},
) {
if (!SAFE_CAMPAIGN.test(campaignId))
throw new Error("Unsafe protocol eval campaign ID");
const { files, campaign } = await validatePublicProtocolEvalReport(
reportRoot,
{ viewerRoot },
);
if (campaign.campaignId !== campaignId)
throw new Error("Report campaign ID does not match publication target");
const entries = await Promise.all(View on GitHub (pinned to 01ad858492)