{"record":{"id":"e76b8e8895aaf3f5","repo":"farion1231/cc-switch","slug":"pi-form-modelidrequired","errorCode":null,"errorMessage":"pi.form.modelIdRequired","messagePattern":"pi\\.form\\.modelIdRequired","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/a2e22f330273a5b6ffa87cb8b82b624601bac562/src/components/providers/forms/PiProviderForm.tsx#L1093-L1129","documentation":"Thrown per-model inside PiProviderForm's submit() normalization loop when a model row has an empty id (PiProviderForm.tsx:1109-1116). Pi treats model IDs as opaque strings that are NOT trimmed (comment at line 1112-1113: trimming would rename an imported model), so only a truly empty string fails. The message interpolates the 1-based row index; fieldSelector '#pi-model-id-{model.key}' plus revealAdvanced=true expands that row's details and focuses the ID input.","triggerScenarios":"Adding a model row and saving without typing an ID; clearing the ID of a row imported from a preset; whitespace-only IDs DO pass (opaque strings), but the empty string does not.","commonSituations":"User fills display name but skips the machine ID; duplicate-UI where rows are added by button then left blank; importing provider JSON where a model entry lacked its id field.","solutions":["Fill in the model ID (the endpoint's exact model identifier) on the row indicated by the error index","Remove the blank row if it was added accidentally","Filter or block submission client-side when any row's id === ''"],"exampleFix":"// before\nconst rows = [{ id: \"\", name: \"My model\" }];\n\n// after\nconst rows = [{ id: \"gpt-5\", name: \"My model\" }];","handlingStrategy":"validation","validationCode":"const emptyRow = models.findIndex((m) => m.id === \"\");\nif (emptyRow !== -1) {\n  setFormError(t(\"pi.form.modelIdRequired\", { index: emptyRow + 1 }));\n  return;\n}","typeGuard":"const hasModelId = (m: { id: string }): boolean => m.id.length > 0;","tryCatchPattern":"try {\n  await submit(identity);\n} catch (error) {\n  if (error instanceof Error && error.name === \"PiFormValidationError\") { /* error.fieldSelector targets '#pi-model-id-{key}' and the form auto-expands that row */ return; }\n  throw error;\n}","preventionTips":["Validate id !== '' per row as the user types and mark the row inline","Offer a 'remove row' action that is easier than saving with blanks","Do NOT trim ids before checking — Pi treats them as opaque strings"],"tags":["form-validation","pi","models","model-id"],"backgroundTag":null,"analyzedSha":"a2e22f330273a5b6ffa87cb8b82b624601bac562","analyzedAt":"2026-08-16T03:46:07.889Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}