{"record":{"id":"a316e5c2c218de8f","repo":"paperclipai/paperclip","slug":"unsupported-live-roster-schema-in-rosterfile","errorCode":null,"errorMessage":"Unsupported live roster schema in ${rosterFile}","messagePattern":"Unsupported live roster schema in (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/scripts/runner-protocol-eval-campaign.mjs","lineNumber":150,"sourceCode":"  const rosterFiles = (await readdir(rosterRoot, { withFileTypes: true }))\n    .filter(\n      (entry) =>\n        entry.isFile() &&\n        entry.name.startsWith(\"live-\") &&\n        entry.name.endsWith(\".json\"),\n    )\n    .map((entry) => entry.name)\n    .sort();\n  const rosters = [];\n  for (const rosterFile of rosterFiles) {\n    const rosterPath = resolve(rosterRoot, rosterFile);\n    const roster = await loadObject(rosterPath);\n    const rosterId = safeId(roster.id, \"roster ID\");\n    if (selected && !selected.has(rosterId) && !selected.has(rosterFile)) {\n      continue;\n    }\n    if (roster.schema !== \"paperclip-runner/live-roster/v1\") {\n      throw new Error(`Unsupported live roster schema in ${rosterFile}`);\n    }\n    if (!Array.isArray(roster.cases) || roster.cases.length === 0) {\n      throw new Error(`Live roster ${rosterId} has no cases`);\n    }\n    const configPath = inside(\n      programRoot,\n      resolve(rosterRoot, String(roster.config ?? \"\")),\n      `Config for ${rosterId}`,\n    );\n    const config = await loadObject(configPath);\n    const credentialName = credentialForConfig(config);\n    const cases = roster.cases.map((caseId) => safeId(caseId, \"case ID\"));\n    if (new Set(cases).size !== cases.length) {\n      throw new Error(`Live roster ${rosterId} repeats a case`);\n    }\n    rosters.push({\n      rosterId,\n      rosterFile,","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/scripts/runner-protocol-eval-campaign.mjs#L132-L168","documentation":"While iterating roster files, buildProtocolEvalCatalog loads each roster and requires its schema field to equal \"paperclip-runner/live-roster/v1\". Any other value (or a missing schema) is rejected with the offending roster filename. This guarantees every selected roster follows the current live-roster contract before cases are processed.","triggerScenarios":"A JSON file in the rosters directory has schema set to another string, an older version like 'live-roster', or lacks the schema field entirely while being selected by the roster selection.","commonSituations":"Old roster files left in the rosters/ directory from a previous contract; a hand-written roster missing the schema header; copying a campaign-shaped JSON into the rosters directory; renamed schema after a version bump.","solutions":["Add or correct \"schema\": \"paperclip-runner/live-roster/v1\" at the top level of the named roster file.","Remove or move stale rosters that follow an older contract out of the rosters/ directory.","If the roster schema was intentionally versioned up, update the check (and any schema-dependent parsing) in buildProtocolEvalCatalog."],"exampleFix":"// before\n{ \"id\": \"codex-core\", \"cases\": [...] }\n// after\n{ \"schema\": \"paperclip-runner/live-roster/v1\", \"id\": \"codex-core\", \"cases\": [...] }","handlingStrategy":"validation","validationCode":"const roster = JSON.parse(await readFile(rosterPath, \"utf8\"));\nif (roster.schema !== \"paperclip-runner/live-roster/v1\") throw new Error(`Unsupported roster schema in ${rosterPath}`);","typeGuard":"const isLiveRosterV1 = (r) => r?.schema === \"paperclip-runner/live-roster/v1\" && Array.isArray(r.cases) && r.cases.length > 0;","tryCatchPattern":"try {\n  const catalog = await buildProtocolEvalCatalog(opts);\n} catch (err) {\n  if (err.message.startsWith(\"Unsupported live roster schema\")) console.error(`Fix the schema field in the roster named in the error.`);\n  throw err;\n}","preventionTips":["Template new rosters from an existing v1 roster so the schema header is never omitted.","Validate all files in rosters/ against live-roster/v1 in CI.","Archive old-contract rosters outside the rosters/ directory so they are never scanned."],"tags":["schema","roster","validation"],"backgroundTag":"unsupported-schema-version","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}