farion1231/cc-switch · error · PiFormValidationError
Model ID {{id}} is duplicated
Error message
Model ID {{id}} is duplicated What it means
PiProviderForm deduplicates model ids per provider: it walks normalizedModels with a seen Set and throws pi.form.duplicateModel (with the offending id, focusing #pi-model-id-<rowKey>) when seen.has(id) is true. Duplicate ids would make Pi's per-model routing and settings ambiguous, so the form rejects them before persisting. Comparison is exact and case-sensitive because ids are opaque strings.
Source
Thrown at src/components/providers/forms/PiProviderForm.tsx:1118
true,
);
}
const headers = normalizeRequestHeaders(providerHeaders);
const seen = new Set<string>();
const normalizedModels = models.map((model, index) => {
// Pi treats model IDs as opaque strings. Trimming would rename an
// imported model.
const id = model.id;
if (id.length === 0) {
throw new PiFormValidationError(
t("pi.form.modelIdRequired", { index: index + 1 }),
`#pi-model-id-${model.key}`,
true,
);
}
if (seen.has(id)) {
throw new PiFormValidationError(
t("pi.form.duplicateModel", { id }),
`#pi-model-id-${model.key}`,
true,
);
}
seen.add(id);
const displayName = model.name.trim();
const includeName = !isEdit || model.hasName;
const includeReasoning = !isEdit || model.hasReasoning;
const includeInput = !isEdit || model.hasInput;
const includeContextWindow = !isEdit || model.hasContextWindow;
const includeMaxTokens = !isEdit || model.hasMaxTokens;
if (includeName && !displayName) {
throw new PiFormValidationError(
t("pi.form.modelNameRequired", { index: index + 1 }),
`#pi-model-name-${model.key}`,
true,
);View on GitHub (pinned to 0b5da51016)
Solutions
- Find the second row whose ID matches {id} in the error and either change it to a genuinely different model id or delete the duplicate row.
- If you need two configurations of the same model, that is not supported at the id level — pick one or use Pi-side profiles.
- Audit the model table for near-identical rows before submitting large imported lists.
Example fix
// before
const models = [
{ key: "m1", id: "gemini-2.5-pro", name: "Fast" },
{ key: "m2", id: "gemini-2.5-pro", name: "Slow" }, // duplicate id
];
submit(identity); // throws "Model ID gemini-2.5-pro is duplicated"
// after
const models = [
{ key: "m1", id: "gemini-2.5-pro", name: "Fast" },
{ key: "m2", id: "gemini-2.5-flash", name: "Slow" },
];
submit(identity); Defensive patterns
Strategy: validation
Validate before calling
const ids = models.map((m) => m.id);
const dup = ids.find((id, i) => ids.indexOf(id) !== i);
if (dup) markRows(`[data-id="${dup}"]`, "Duplicate model id"); Try / catch
catch (e) {
if (e instanceof PiFormValidationError) { setFormError(e.message); /* e.fieldSelector targets the duplicate row */ document.querySelector(e.fieldSelector ?? "")?.focus(); return; }
throw e;
} Prevention
- Show a live 'duplicate' badge on rows whose id already exists in the list.
- Run a Set-based dedupe pass over imported model lists before hydrating the form.
When it happens
Trigger: Two rows both containing id 'gpt-4o'; pasting the same model twice; copy-duplicating a row and editing only its display name; ids that differ only by whitespace are NOT duplicates (no trimming) but 'X' vs 'x' ARE distinct, so case-typos like 'GPT-4o' vs 'gpt-4o' pass while exact repeats fail.
Common situations: Adding the same underlying model with different display names for different reasoning settings; import merges that concat model lists; manual re-entry forgetting a row already exists.
Related errors
- Model {{index}} needs an ID
- {{label}} must be an absolute HTTP or HTTPS URL
- {{label}} must be a number greater than 0
- Select a preset or custom configuration first
- Configuration must be a JSON object, not an array or other t
AI-assisted analysis of farion1231/cc-switch@0b5da51016 (2026-08-20).
Data as JSON: /api/errors/853b726ff9ab6f89.
Report an issue: GitHub.