{"record":{"id":"c1cf36bfe21dfcd5","repo":"paperclipai/paperclip","slug":"public-report-campaign-metadata-is-invalid","errorCode":null,"errorMessage":"Public report campaign metadata is invalid","messagePattern":"Public report campaign metadata is invalid","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/scripts/publish-runner-protocol-eval-history.mjs","lineNumber":237,"sourceCode":"        );\n      }\n    }\n  }\n  if (viewer) {\n    for (const file of viewer.files.keys())\n      if (!files.includes(file))\n        throw new Error(`Missing public viewer asset: ${file}`);\n    if (files.some((file) => /^attempts\\/[^/]+\\.html$/.test(file)))\n      throw new Error(\n        \"Chat Evalbook must not mix in legacy plain attempt pages\",\n      );\n  }\n  const campaign = await loadObject(join(root, \"campaign.json\"));\n  if (\n    campaign.schema !== \"paperclip.runner-protocol-eval.campaign/v1\" ||\n    !SAFE_CAMPAIGN.test(String(campaign.campaignId ?? \"\"))\n  ) {\n    throw new Error(\"Public report campaign metadata is invalid\");\n  }\n  return { files, campaign };\n}\n\nexport async function createProtocolEvalBundleManifest(\n  reportRoot,\n  campaignId,\n  { viewerRoot } = {},\n) {\n  if (!SAFE_CAMPAIGN.test(campaignId))\n    throw new Error(\"Unsafe protocol eval campaign ID\");\n  const { files, campaign } = await validatePublicProtocolEvalReport(\n    reportRoot,\n    { viewerRoot },\n  );\n  if (campaign.campaignId !== campaignId)\n    throw new Error(\"Report campaign ID does not match publication target\");\n  const entries = await Promise.all(","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/scripts/publish-runner-protocol-eval-history.mjs#L219-L255","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before (campaign.json)\n{ \"schema\": \"campaign/v2\", \"campaignId\": \"local_run_42\" }\n// after\n{ \"schema\": \"paperclip.runner-protocol-eval.campaign/v1\", \"campaignId\": \"gha-42-1\" }","handlingStrategy":"validation","validationCode":"const campaign = JSON.parse(fs.readFileSync(path.join(reportRoot, \"campaign.json\"), \"utf8\"));\nconst SAFE = /^gha-[1-9][0-9]*-[1-9][0-9]*(-report-[a-z0-9][a-z0-9-]{0,39})?$/;\nif (campaign.schema !== \"paperclip.runner-protocol-eval.campaign/v1\" || !SAFE.test(String(campaign.campaignId)))\n  throw new Error(\"invalid campaign.json before publish\");","typeGuard":"const isValidCampaign = (c) =>\n  !!c && typeof c === \"object\" && !Array.isArray(c) &&\n  c.schema === \"paperclip.runner-protocol-eval.campaign/v1\" &&\n  /^gha-[1-9][0-9]*-[1-9][0-9]*(-report-[a-z0-9][a-z0-9-]{0,39})?$/.test(String(c.campaignId ?? \"\"));","tryCatchPattern":"try {\n  await publishProtocolEvalHistory({ reportRoot, destination, viewerRoot });\n} catch (e) {\n  if (String(e.message).includes(\"campaign metadata is invalid\")) {\n    console.error(\"check campaign.json schema/campaignId:\", fs.readFileSync(path.join(reportRoot, \"campaign.json\"), \"utf8\"));\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["validation","json","schema"],"backgroundTag":"schema-validation-failed","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}