{"record":{"id":"3ac55890b186dd42","repo":"paperclipai/paperclip","slug":"history-has-no-latest-campaign","errorCode":null,"errorMessage":"History has no latest campaign","messagePattern":"History has no latest campaign","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"scripts/evals-hub/build.py","lineNumber":24,"sourceCode":"import json\nfrom pathlib import Path\nfrom string import Template\nfrom urllib.parse import quote, urlsplit\nfrom urllib.request import urlopen\n\nORIGIN = \"https://d1p6rlowie26tp.cloudfront.net\"\nSYSTEMS = {\n    \"runner\": (\"runner-protocol-evals\", \"paperclip.runner-protocol-eval.history/v1\"),\n    \"product\": (\"runner-e2e\", \"paperclip.runner-e2e.history/v1\"),\n}\n\n\ndef latest(history, schema):\n    if history.get(\"schema\") != schema:\n        raise ValueError(\"Unsupported history schema\")\n    campaign_id = history.get(\"latestCampaignId\")\n    if not campaign_id:\n        raise ValueError(\"History has no latest campaign\")\n    candidates = [c for c in history[\"campaigns\"] if\n                  c[\"campaignId\"] == campaign_id or\n                  c.get(\"reportRevision\", {}).get(\"sourceCampaignId\") == campaign_id]\n    if not candidates:\n        raise ValueError(\"Latest campaign is absent from history\")\n    # Report refreshes replace presentation, never the measurement date.\n    return max(candidates, key=lambda c: c.get(\"reportRevision\", {}).get(\"renderedAt\", c[\"generatedAt\"]))\n\n\ndef public_report(url, prefix):\n    parsed = urlsplit(url)\n    if (parsed.scheme != \"https\" or parsed.netloc != urlsplit(ORIGIN).netloc\n            or not parsed.path.startswith(f\"/{prefix}/campaigns/\")\n            or parsed.query or parsed.fragment):\n        raise ValueError(\"Unexpected public report URL\")\n    return html.escape(url, quote=True)\n\n","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/scripts/evals-hub/build.py#L6-L42","documentation":"After schema validation, latest() reads history['latestCampaignId'] to know which campaign the published report should show. If the field is missing, null, or empty, there is no authoritative 'latest' pointer, and rather than silently falling back to an arbitrary campaign the script raises ValueError.","triggerScenarios":"A history JSON generated before any campaign ran (campaigns list empty or pointer never set), a truncated/partial upload of history.json, or a writer bug that omits latestCampaignId while still listing campaigns.","commonSituations":"Freshly initialized eval bucket with no campaigns yet; publisher crashed mid-update leaving history without the pointer; manually constructed fixtures for tests that forgot latestCampaignId.","solutions":["Re-run the campaign publisher so history.json includes a non-empty latestCampaignId.","If this is a legitimate no-campaigns-yet state, guard the build: skip rendering (or emit an empty page) until a campaign exists.","Fix the writer that publishes history to always set latestCampaignId atomically with campaigns.","When testing offline, add latestCampaignId to your fixture matching one of campaigns[].campaignId."],"exampleFix":"# before\nhistory = {\"schema\": ..., \"campaigns\": [...]}  # no latestCampaignId\n# after\nhistory = {\"schema\": ..., \"latestCampaignId\": \"c-2026-09-18\", \"campaigns\": [...]}","handlingStrategy":"validation","validationCode":"def has_latest(history):\n    return bool(history.get(\"latestCampaignId\"))","typeGuard":"def has_latest_campaign(history):\n    return isinstance(history.get(\"latestCampaignId\"), str) and history[\"latestCampaignId\"] != \"\"","tryCatchPattern":"try:\n    values = summarize(history, kind)\nexcept ValueError as e:\n    if str(e) == \"History has no latest campaign\":\n        return None  # no campaigns published yet; render empty page\n    raise","preventionTips":["Only build the page once at least one campaign has been published.","Ensure the publisher sets latestCampaignId atomically with the campaigns list.","Validate history.json after every publish with a small schema check.","Fixtures for tests must always include latestCampaignId."],"tags":["validation","missing-field","python","feed"],"backgroundTag":"missing-config-key","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}