paperclipai/paperclip · error · ValueError

Missing campaign coverage flag

Error message

Missing campaign coverage flag

What it means

Every campaign must carry a boolean `complete` flag indicating full vs partial coverage, which the page renders as 'Complete campaign' or 'Partial campaign'. If the key is missing or not a bool, summarize() refuses to guess and raises ValueError.

Solutions

  1. Re-publish the campaign with an explicit boolean `complete` field.
  2. Fix the producer to always set complete (true/false) on every campaign record.
  3. If consuming a legacy feed, migrate it: add `complete` derived from the run's selection coverage.
  4. Update offline fixtures in --history-dir to include complete: true/false.

Example fix

# before
{"campaignId": "c-1", "generatedAt": "..."}  # no complete flag
# after
{"campaignId": "c-1", "generatedAt": "...", "complete": true}
Defensive patterns

Strategy: validation

Validate before calling

def has_complete_flag(campaign):
    return type(campaign.get("complete")) is bool

Type guard

def has_bool_complete(campaign):
    return type(campaign.get("complete")) is bool

Try / catch

try:
    values = summarize(history, kind)
except ValueError as e:
    if str(e) == "Missing campaign coverage flag":
        log.error("campaign missing boolean `complete`; migrate feed before building")
        raise
    raise

Prevention

When it happens

Trigger: A campaign entry lacking the `complete` key, or storing it as a string ('true'), int (1/0), or null — e.g. older feeds predating the flag, or a producer serializing the wrong type.

Common situations: Old history fixtures generated before the coverage flag was introduced; publisher bug omitting the field for resumed campaigns; schema drift between feed versions.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

    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")


def summarize(history, kind):
    prefix, schema = SYSTEMS[kind]
    campaign = latest(history, schema)
    totals = campaign["totals"] if kind == "runner" else campaign
    passed, selected = totals["passed"], totals["selected"]
    if any(type(n) is not int for n in (passed, selected)) or not 0 <= passed <= selected:
        raise ValueError("Invalid campaign counts")
    if type(campaign.get("complete")) is not bool:
        raise ValueError("Missing campaign coverage flag")
    revision = campaign.get("reportRevision", {})
    if revision and not isinstance(revision.get("sourceGeneratedAt"), str):
        raise ValueError("Report refresh has no reliable measurement date")
    measured = revision.get("sourceGeneratedAt", campaign["generatedAt"])
    return {
        "url": public_report(campaign["publicUrl"], prefix),
        "date": html.escape(display_date(measured)),
        "counts": f"{passed} / {selected} selected cases passed",
        "coverage": "Complete campaign" if campaign["complete"] else "Partial campaign",
        "id": html.escape(revision.get("sourceCampaignId", campaign["campaignId"])),
    }


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--output", type=Path, required=True)
    parser.add_argument("--history-dir", type=Path, help="Use saved public history JSON files for offline verification")
    parser.add_argument("--docs-ref", default="master", help="Git ref containing doc/evals.md")

View on GitHub (pinned to 3f1d897a7c)