{"record":{"id":"46ed6363a03f2172","repo":"tinyhumansai/openhuman","slug":"settings-ai-slugmissingerror-settings-ai-sluginuse","errorCode":null,"errorMessage":"settings.ai.slugMissingError|settings.ai.slugInUseError|settings.ai.slugReservedError","messagePattern":"settings\\.ai\\.slugMissingError\\|settings\\.ai\\.slugInUseError\\|settings\\.ai\\.slugReservedError","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/src/components/settings/panels/AIPanel.tsx","lineNumber":3906,"sourceCode":"  // Skipping verification is a bet that the provider works despite an\n  // unreadable listing. For an Azure host that is not the `/openai/v1` base\n  // that bet is already lost: `{base}/chat/completions` is not a route Azure\n  // serves there and the stored bearer auth is the wrong header, so the entry\n  // would be dead on arrival. Withhold the bypass and let the inline nudge do\n  // its job instead of manufacturing a broken provider (#5213).\n  const knownUnusableEndpoint =\n    isAzureFoundryEndpoint(endpoint) && !isAzureV1BaseUrl(endpoint.trim());\n\n  const submitProvider = async (opts?: { skipProbe?: boolean }) => {\n    setSaving(true);\n    setSubmitError(null);\n    // Cleared alongside the error: a later attempt that fails for an unrelated\n    // reason (slug collision, key write) must not still offer to skip\n    // verification, which is the distinction `ProviderProbeError` exists for.\n    setProbeFailed(false);\n    try {\n      if (slugError) {\n        throw new Error(slugError);\n      }\n      await onSubmit(\n        {\n          id: initial?.id ?? '',\n          slug,\n          label: label.trim() || slug,\n          endpoint: endpoint.trim(),\n          authStyle: initial?.authStyle ?? 'bearer',\n          maskedKey: maskKeyLabel(hasExistingKey || apiKey.length > 0),\n        },\n        apiKey.trim(),\n        opts\n      );\n    } catch (err) {\n      // Surface the failure inline and keep the dialog open so the user can fix\n      // the key/URL and retry. A rejected `/models` probe additionally unlocks\n      // the \"add without verifying\" button — the listing is a convenience for\n      // the model dropdown, not a precondition for inference.","sourceCodeStart":3888,"sourceCodeEnd":3924,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/components/settings/panels/AIPanel.tsx#L3888-L3924","documentation":"submitProvider throws a precomputed slugError before ever calling onSubmit. slugError is derived in three ordered steps (AIPanel.tsx:3880-3886): slug empty (label slugifies to nothing) -> settings.ai.slugMissingError; slug already present in existingSlugs -> settings.ai.slugInUseError; adding (not editing, !initial) a slug in BUILTIN_RESERVED_SLUGS -> settings.ai.slugReservedError. The same slugError is rendered inline under the field and disables the Save button, so this throw only fires on programmatic submits or racing state.","triggerScenarios":"A custom-provider label whose slugified form is empty (label is only emoji/punctuation/spaces); a label that slugifies identically to an existing custom provider's slug; or creating a new custom provider whose label collides with a built-in slug (e.g. 'openai') - reserved check is skipped when editing an existing entry.","commonSituations":"Users naming a provider 'OpenAI' or 'Anthropic' trying to override a built-in; two providers named 'My GPT' and 'my-gpt'; labels in scripts/emoji that produce no slug characters; UI state where the inline error is set but the disabled Save is bypassed (double-click race before re-render).","solutions":["Change the label so its slug differs - add a distinguishing word or number","To configure a built-in provider, edit the built-in entry instead of creating a custom one with its slug","When editing an existing entry, the reserved check no longer applies - only emptiness and duplicates do","If hit programmatically, compute slugifyCustomProviderName(label) first and assert it is non-empty and unused"],"exampleFix":"// before - rely on the throw inside submitProvider\nawait submitProvider();\n\n// after - gate the call on the same conditions the form uses\nconst slug = slugifyCustomProviderName(label);\nif (!slug || existingSlugs.includes(slug) || (!initial && BUILTIN_RESERVED_SLUGS.includes(slug))) {\n  return; // field-level slugError already tells the user why\n}\nawait submitProvider();","handlingStrategy":"validation","validationCode":"// Reproduce the form's gate before submitting programmatically\nconst slug = slugifyCustomProviderName(label);\nconst slugError = !slug\n  ? t('settings.ai.slugMissingError')\n  : existingSlugs.includes(slug)\n    ? t('settings.ai.slugInUseError')\n    : !initial && BUILTIN_RESERVED_SLUGS.includes(slug)\n      ? t('settings.ai.slugReservedError')\n      : null;\nif (slugError) { setSubmitError(slugError); return; }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Give custom providers distinctive labels - built-in names like 'openai' are reserved for new entries","Remember editing an existing entry skips only the reserved check; duplicate and empty slugs still block","Labels made solely of emoji/punctuation slugify to nothing - include at least one word character"],"tags":["ai-settings","provider","slug","validation","reserved-names","i18n"],"backgroundTag":"duplicate-slug-collision","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}