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

  1. 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.
  2. If you need two configurations of the same model, that is not supported at the id level — pick one or use Pi-side profiles.
  3. 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

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


AI-assisted analysis of farion1231/cc-switch@0b5da51016 (2026-08-20). Data as JSON: /api/errors/853b726ff9ab6f89. Report an issue: GitHub.