paperclipai/paperclip · error · Error
Unsupported live campaign schema in
Error message
Unsupported live campaign schema in ${campaignPath} What it means
maintainedRosterSelection loads campaigns/live-direct-full.json from the program root and requires it to declare schema "paperclip-runner/live-campaign/v1" and an array 'lanes'. Any other shape is rejected with the campaign path in the message. This keeps the maintained campaign contract explicit so downstream lane filtering behaves predictably.
Solutions
- Set "schema": "paperclip-runner/live-campaign/v1" at the top level of campaigns/live-direct-full.json.
- Ensure the campaign defines lanes as an array of lane objects.
- If the schema intentionally changed, update maintainedRosterSelection (and related loaders) to accept the new schema version.
Example fix
// before
{ "schema": "live-campaign", "laneList": [...] }
// after
{ "schema": "paperclip-runner/live-campaign/v1", "lanes": [...] } Defensive patterns
Strategy: validation
Validate before calling
const campaign = JSON.parse(await readFile(campaignPath, "utf8"));
if (campaign.schema !== "paperclip-runner/live-campaign/v1" || !Array.isArray(campaign.lanes)) throw new Error(`Unsupported campaign schema in ${campaignPath}`); Type guard
const isLiveCampaignV1 = (c) => c?.schema === "paperclip-runner/live-campaign/v1" && Array.isArray(c.lanes);
Try / catch
try {
const selection = await maintainedRosterSelection(programRoot);
} catch (err) {
if (err.message.startsWith("Unsupported live campaign schema")) console.error("Check campaigns/live-direct-full.json schema field and lanes array.");
throw err;
} Prevention
- Pin the schema string in a shared constant and import it in both writer and reader.
- Validate campaign files in CI against the live-campaign/v1 shape.
- When bumping schema versions, migrate existing campaign files in the same change.
When it happens
Trigger: Calling maintainedRosterSelection when the campaign file's schema field is missing, set to another version string, or when campaign.lanes is not an array (absent, an object, or null).
Common situations: Campaign file edited to a newer/renamed schema string without updating the script; lanes field renamed or restructured; stale campaign file from an older layout; file replaced with a differently-shaped export.
Related errors
- does not carry submitted form data
- : invalid AppDefinition
- Durable PRP run attachment template is invalid.
- invalid question response
- Invalid semantic result
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/1a9016cef1f66417.
Report an issue: GitHub.
Appendix: source
Thrown at packages/paperclip-runner/scripts/runner-protocol-eval-campaign.mjs:93
if (!value?.trim() || value.trim() === "all") return null;
const selected = value
.split(",")
.map((entry) => entry.trim())
.filter(Boolean);
if (selected.length === 0 || new Set(selected).size !== selected.length) {
throw new Error("Roster selection must contain unique comma-separated IDs");
}
return new Set(selected);
}
async function maintainedRosterSelection(programRoot) {
const campaignPath = resolve(programRoot, "campaigns/live-direct-full.json");
const campaign = await loadObject(campaignPath);
if (
campaign.schema !== "paperclip-runner/live-campaign/v1" ||
!Array.isArray(campaign.lanes)
) {
throw new Error(`Unsupported live campaign schema in ${campaignPath}`);
}
const selected = campaign.lanes
.filter((lane) => lane.executionClass !== "disabled")
.map((lane) => {
const rosterPath = inside(
programRoot,
resolve(dirname(campaignPath), String(lane.roster ?? "")),
"Campaign roster",
);
return basename(rosterPath);
});
if (selected.length === 0 || new Set(selected).size !== selected.length) {
throw new Error(
"Maintained live campaign must contain unique enabled rosters",
);
}
return new Set(selected);
}View on GitHub (pinned to 01ad858492)