{"record":{"id":"75c768138bb72e2d","repo":"paperclipai/paperclip","slug":"public-protocol-eval-report-requires-index-html-an","errorCode":null,"errorMessage":"Public protocol eval report requires index.html and campaign.json","messagePattern":"Public protocol eval report requires index\\.html and campaign\\.json","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/scripts/publish-runner-protocol-eval-history.mjs","lineNumber":151,"sourceCode":"\nfunction internalHtmlHrefs(content) {\n  return [...content.matchAll(/href\\s*=\\s*[\"']([^\"']+)[\"']/giu)]\n    .map((match) => match[1])\n    .filter((href) => href && !href.startsWith(\"#\"));\n}\n\nexport async function validatePublicProtocolEvalReport(\n  reportRoot,\n  { viewerRoot } = {},\n) {\n  const root = resolve(reportRoot);\n  const files = await relativeFiles(root);\n  const hasChat = files.some((file) =>\n    /^attempts\\/[^/]+\\/index\\.html$/.test(file),\n  );\n  const viewer = hasChat ? await trustedViewerFiles(viewerRoot) : null;\n  if (!files.includes(\"index.html\") || !files.includes(\"campaign.json\")) {\n    throw new Error(\n      \"Public protocol eval report requires index.html and campaign.json\",\n    );\n  }\n  for (const file of files) {\n    if (!isPublicProtocolEvalPath(file)) {\n      throw new Error(\n        `Refusing non-allowlisted public protocol eval path ${file}`,\n      );\n    }\n    const absolute = resolve(root, ...file.split(\"/\"));\n    const metadata = await stat(absolute);\n    if (metadata.size === 0 || metadata.size > 12 * 1024 * 1024) {\n      throw new Error(\n        `Public protocol eval file exceeds its size boundary: ${file}`,\n      );\n    }\n    if (file.startsWith(\"viewer/\")) {\n      const expected = viewer?.files.get(file);","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/scripts/publish-runner-protocol-eval-history.mjs#L133-L169","documentation":"After collecting all files under the public report root, validatePublicProtocolEvalReport checks that the report is structurally complete: a top-level index.html and a top-level campaign.json must both exist. If either is missing the script refuses to publish, since a public report without the entry page or campaign metadata is unusable for viewers.","triggerScenarios":"Publishing a directory that lacks index.html at its root, or lacks campaign.json at its root (e.g. pointing the script at an attempts/ subdirectory or a partial build output).","commonSituations":"Passing the wrong directory (the attempts folder instead of the report root); a build step failed before writing index.html/campaign.json; campaign.json was renamed or deleted; a report produced by a different/older generator layout.","solutions":["Verify you passed the report ROOT directory (the one containing index.html and campaign.json), not a subdirectory.","Regenerate the report so both index.html and campaign.json are produced before publishing.","Check the report generation step's logs for a failure that skipped writing campaign.json or index.html."],"exampleFix":"// before\nawait publish({ reportDir: 'public-report/attempts' });\n// after\nawait publish({ reportDir: 'public-report' }); // contains index.html + campaign.json","handlingStrategy":"validation","validationCode":"import { existsSync } from 'node:fs';\nif (!existsSync(`${reportRoot}/index.html`) || !existsSync(`${reportRoot}/campaign.json`)) {\n  throw new Error('report root must contain index.html and campaign.json');\n}","typeGuard":"function isCompleteReportRoot(fs, dir) {\n  return fs.existsSync(`${dir}/index.html`) && fs.existsSync(`${dir}/campaign.json`);\n}","tryCatchPattern":"try {\n  await publishReport(root);\n} catch (err) {\n  if (err.message === 'Public protocol eval report requires index.html and campaign.json') {\n    console.error('Wrong or incomplete report dir; regenerate or point at the report root.');\n    process.exitCode = 1;\n  } else throw err;\n}","preventionTips":["Always pass the report root, never a subdirectory like attempts/.","Add a pre-publish check asserting index.html and campaign.json exist.","Verify the report generator completed successfully (exit code, output logs) before publishing."],"tags":["validation","publish-script","missing-file"],"backgroundTag":"file-not-found","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"}