{"record":{"id":"b2e4b4ebbe8d5330","repo":"paperclipai/paperclip","slug":"unknown-public-chat-payload-field","errorCode":null,"errorMessage":"Unknown public chat payload field","messagePattern":"Unknown public chat payload field","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/scripts/public-eval-viewer.mjs","lineNumber":83,"sourceCode":"    payload.devtools !== null\n  )\n    throw new Error(\n      \"Public attempt must contain the read-only public chat projection\",\n    );\n  const allowed = new Set([\n    \"attemptId\",\n    \"caseId\",\n    \"disposition\",\n    \"passed\",\n    \"checks\",\n    \"view\",\n    \"devtools\",\n    \"navigation\",\n    \"run\",\n    \"publication\",\n  ]);\n  if (Object.keys(payload).some((key) => !allowed.has(key)))\n    throw new Error(\"Unknown public chat payload field\");\n  const fields = (value, names) => {\n    if (\n      !value ||\n      typeof value !== \"object\" ||\n      Array.isArray(value) ||\n      Object.keys(value).some((key) => !names.split(\" \").includes(key))\n    )\n      throw new Error(\"Unknown public chat projection field\");\n  };\n  fields(payload.publication, \"schema notice\");\n  fields(payload.navigation, \"suiteHref previous next\");\n  for (const link of [payload.navigation.previous, payload.navigation.next])\n    if (link !== null) fields(link, \"label href\");\n  fields(\n    payload.run,\n    \"model provider driver providerVersion runnerProvider acpxAgent acpxProfile requestedModel effectiveModelHistory configuration sessionId providerSessionId agentVersion managedProfile retainedSession retainedSessionStatus fixtureDigest runnerPackageDigest runnerdDigest startedAt finishedAt durationMs runnerBuild initialRevision finalRevision usage\",\n  );\n  if (","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/scripts/public-eval-viewer.mjs#L65-L101","documentation":"After the projection gate, validatePublicChatPayload() enforces a strict allowlist of top-level payload keys (attemptId, caseId, disposition, passed, checks, view, devtools, navigation, run, publication). If any other key is present — even an innocuous extra field — \"Unknown public chat payload field\" is thrown. This fail-closed check prevents newly added internal fields from leaking into public reports before being explicitly allowlisted.","triggerScenarios":"Calling validatePublicChatPayload with a payload containing any top-level key outside the allowlist, e.g. an internal \"runLog\", \"environment\", or debug field added by the generator, or a deprecated field not yet removed after a schema change.","commonSituations":"A developer adds a new field to the run payload generator and forgets to allowlist it here; an older generator emits a field that was removed from the allowlist; serialization layers attach metadata (e.g. \"meta\", \"version\") to the payload object.","solutions":["Remove the unexpected top-level field from the payload before validation.","If the generator intentionally added a new public field, add its name to the allowed Set in validatePublicChatPayload().","Confirm the generator version matches the validator; regenerate the payload with the current generator.","Check that payload post-processing (spreading extra objects into the payload) is not merging internal metadata into the public payload."],"exampleFix":"// before\nconst payload = { ...publicProjection, runLog: raw.runLog };\n// after\nconst payload = { ...publicProjection }; // no extra top-level fields","handlingStrategy":"validation","validationCode":"const ALLOWED = new Set([\"attemptId\",\"caseId\",\"disposition\",\"passed\",\"checks\",\"view\",\"devtools\",\"navigation\",\"run\",\"publication\"]);\nconst extras = Object.keys(payload).filter((k) => !ALLOWED.has(k));\nif (extras.length) console.warn(\"Remove extra top-level fields:\", extras);","typeGuard":"const hasOnlyAllowedKeys = (p, allowed) => p != null && Object.keys(p).every((k) => allowed.has(k));","tryCatchPattern":"try {\n  validatePublicChatPayload(payload);\n} catch (err) {\n  if (err.message === \"Unknown public chat payload field\") {\n    console.error(\"Top-level allowlist mismatch; regenerate payload or update the allowlist\", Object.keys(payload));\n  }\n  throw err;\n}","preventionTips":["When adding a payload field, update the generator, the allowlist, and tests in one commit","Avoid spreading whole internal payload objects into the public payload","Keep a single shared constant for the top-level key set used by both producer and validator","Snapshot-test the public payload shape to catch new keys immediately"],"tags":["validation","schema","allowlist"],"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-14T00:17:10.932Z"}