{"record":{"id":"6066f86312a5f968","repo":"farion1231/cc-switch","slug":"model-index-needs-an-id","errorCode":null,"errorMessage":"Model {{index}} needs an ID","messagePattern":"Model (.+?)\\} needs an ID","errorType":"validation","errorClass":"PiFormValidationError","httpStatus":null,"severity":"warning","filePath":"src/components/providers/forms/PiProviderForm.tsx","lineNumber":1111,"sourceCode":"          \"#pi-api-key\",\n        );\n      }\n      if (!isEdit && models.length === 0) {\n        throw new PiFormValidationError(\n          t(\"pi.form.modelRequired\"),\n          \"#pi-add-model\",\n          true,\n        );\n      }\n\n      const headers = normalizeRequestHeaders(providerHeaders);\n      const seen = new Set<string>();\n      const normalizedModels = models.map((model, index) => {\n        // Pi treats model IDs as opaque strings. Trimming would rename an\n        // imported model.\n        const id = model.id;\n        if (id.length === 0) {\n          throw new PiFormValidationError(\n            t(\"pi.form.modelIdRequired\", { index: index + 1 }),\n            `#pi-model-id-${model.key}`,\n            true,\n          );\n        }\n        if (seen.has(id)) {\n          throw new PiFormValidationError(\n            t(\"pi.form.duplicateModel\", { id }),\n            `#pi-model-id-${model.key}`,\n            true,\n          );\n        }\n        seen.add(id);\n        const displayName = model.name.trim();\n        const includeName = !isEdit || model.hasName;\n        const includeReasoning = !isEdit || model.hasReasoning;\n        const includeInput = !isEdit || model.hasInput;\n        const includeContextWindow = !isEdit || model.hasContextWindow;","sourceCodeStart":1093,"sourceCodeEnd":1129,"githubUrl":"https://github.com/farion1231/cc-switch/blob/0b5da510168914b251481654a568c3ffacd62cf4/src/components/providers/forms/PiProviderForm.tsx#L1093-L1129","documentation":"While normalizing the model list, PiProviderForm checks each row's id and throws pi.form.modelIdRequired (with 1-based index, focusing #pi-model-id-<rowKey> and revealing advanced fields) when model.id.length === 0. Unlike the name field, ids are deliberately NOT trimmed — the source comment notes Pi treats model ids as opaque strings — so an id of only whitespace still counts as non-empty, but an untouched empty row fails.","triggerScenarios":"Clicking 'Add model' and saving without typing an id; deleting the id text of an auto-created empty row; importing models where one row's id is \"\".","commonSituations":"Users add a model row to see what it does, fill only the display name, and submit; a duplicate-check flow removed an id; keyboard focus loss leaves the id input blank.","solutions":["Fill the Model ID input of the row indicated by the {index} in the message (e.g. 'Model 2' = second row) with the exact id Pi/APIs expect, such as 'claude-sonnet-4'.","Remove empty placeholder rows you did not intend to configure before submitting.","When importing, skip or populate rows whose id is missing."],"exampleFix":"// before\nconst models = [\n  { key: \"m1\", id: \"\", name: \"My model\" }, // id never filled\n];\nsubmit(identity); // throws 'Model 1 needs an ID'\n\n// after\nconst models = [\n  { key: \"m1\", id: \"gpt-5.2\", name: \"My model\" },\n];\nsubmit(identity);","handlingStrategy":"validation","validationCode":"const emptyIdIndex = models.findIndex((m) => m.id.length === 0);\nif (emptyIdIndex >= 0) focusRow(`#pi-model-id-${models[emptyIdIndex].key}`);","typeGuard":null,"tryCatchPattern":"catch (e) {\n  if (e instanceof PiFormValidationError) { setAdvancedOpen(e.revealAdvanced || advancedOpen); document.querySelector(e.fieldSelector ?? \"\")?.scrollIntoView({ block: \"center\" }); return; }\n  throw e;\n}","preventionTips":["Validate each row's id on blur, not only at submit.","Do not create model rows without ids in import code; filter them out first."],"tags":["pi","form-validation","model-id","react"],"backgroundTag":"required-form-field-missing","analyzedSha":"0b5da510168914b251481654a568c3ffacd62cf4","analyzedAt":"2026-08-20T14:29:00.113Z","contentChangedAt":"2026-08-20T14:29:00.113Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}