paperclipai/paperclip · error · ValueError

Latest campaign is absent from history

Error message

Latest campaign is absent from history

What it means

latest() filters history['campaigns'] for entries matching latestCampaignId either directly (campaignId) or via a reportRevision whose sourceCampaignId points at it. If no entry matches, the pointer references a campaign the list does not contain — an inconsistent feed — and the script raises rather than falling back to an arbitrary run.

Solutions

  1. Re-publish history.json so the campaigns list contains the campaign named by latestCampaignId (or its reportRevision sourceCampaignId).
  2. Fix the publisher/retention logic to update or clear latestCampaignId whenever campaigns are pruned.
  3. If a stale pointer is expected, regenerate the pointer to the newest retained campaignId.
  4. Correct the campaignId in your fixture so it matches an entry in campaigns.

Example fix

# before
{"latestCampaignId": "c-42", "campaigns": [{"campaignId": "c-41", ...}]}
# after
{"latestCampaignId": "c-41", "campaigns": [{"campaignId": "c-41", ...}]}
Defensive patterns

Strategy: validation

Validate before calling

def latest_is_present(history):
    cid = history.get("latestCampaignId")
    return any(c.get("campaignId") == cid or
               c.get("reportRevision", {}).get("sourceCampaignId") == cid
               for c in history.get("campaigns", []))

Type guard

def pointer_resolves(history):
    cid = history.get("latestCampaignId")
    return isinstance(cid, str) and any(
        c.get("campaignId") == cid or c.get("reportRevision", {}).get("sourceCampaignId") == cid
        for c in history.get("campaigns", []))

Try / catch

try:
    values = summarize(history, kind)
except ValueError as e:
    if str(e) == "Latest campaign is absent from history":
        history = refetch_history()  # pointer/list mismatch; re-download consistent feed
        values = summarize(history, kind)
    else:
        raise

Prevention

When it happens

Trigger: latestCampaignId names a campaign that was pruned from the campaigns list (retention/truncation), a reportRevision references a sourceCampaignId that was dropped, or the id was typo'd in a hand-edited fixture.

Common situations: Retention job trimming old campaigns without updating latestCampaignId; partial history upload; fixtures edited by hand where campaignId strings don't match.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/369e568b12814c93. Report an issue: GitHub.

Appendix: source

Thrown at scripts/evals-hub/build.py:29

ORIGIN = "https://d1p6rlowie26tp.cloudfront.net"
SYSTEMS = {
    "runner": ("runner-protocol-evals", "paperclip.runner-protocol-eval.history/v1"),
    "product": ("runner-e2e", "paperclip.runner-e2e.history/v1"),
}


def latest(history, schema):
    if history.get("schema") != schema:
        raise ValueError("Unsupported history schema")
    campaign_id = history.get("latestCampaignId")
    if not campaign_id:
        raise ValueError("History has no latest campaign")
    candidates = [c for c in history["campaigns"] if
                  c["campaignId"] == campaign_id or
                  c.get("reportRevision", {}).get("sourceCampaignId") == campaign_id]
    if not candidates:
        raise ValueError("Latest campaign is absent from history")
    # Report refreshes replace presentation, never the measurement date.
    return max(candidates, key=lambda c: c.get("reportRevision", {}).get("renderedAt", c["generatedAt"]))


def public_report(url, prefix):
    parsed = urlsplit(url)
    if (parsed.scheme != "https" or parsed.netloc != urlsplit(ORIGIN).netloc
            or not parsed.path.startswith(f"/{prefix}/campaigns/")
            or parsed.query or parsed.fragment):
        raise ValueError("Unexpected public report URL")
    return html.escape(url, quote=True)


def display_date(value):
    date = datetime.fromisoformat(value.replace("Z", "+00:00"))
    if date.tzinfo is None:
        raise ValueError("History date must include a timezone")
    return date.astimezone(timezone.utc).strftime("%d %b %Y · %H:%M UTC")

View on GitHub (pinned to 3f1d897a7c)