{"record":{"id":"c4342ed8509ebdc0","repo":"jlcodes99/cockpit-tools","slug":"provider-base-url-exists","errorCode":"PROVIDER_BASE_URL_EXISTS","errorMessage":"PROVIDER_BASE_URL_EXISTS","messagePattern":"PROVIDER_BASE_URL_EXISTS","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/codexModelProviderService.ts","lineNumber":589,"sourceCode":"  boundInstanceId?: string;\n  website?: string;\n  apiKeyUrl?: string;\n  wireApi?: CodexProviderWireApi;\n  supportsWebsockets?: boolean;\n  enableModePreference?: CodexProviderEnableModePreference;\n  integrationType?: 'sub2api' | 'new_api';\n  boundOauthAccountId?: string | null;\n  initialApiKey?: string;\n  initialApiKeyName?: string;\n}): Promise<CodexModelProvider> {\n  const name = sanitizeName(input.name);\n  const baseUrl = normalizeBaseUrlForStore(input.baseUrl);\n  const normalizedBaseUrl = normalizeCodexModelProviderBaseUrl(baseUrl);\n  if (!name) throw new Error('PROVIDER_NAME_REQUIRED');\n  if (!normalizedBaseUrl) throw new Error('PROVIDER_BASE_URL_INVALID');\n  const providers = await ensureProvidersLoaded();\n  if (providers.some((item) => normalizeCodexModelProviderBaseUrl(item.baseUrl) === normalizedBaseUrl)) {\n    throw new Error('PROVIDER_BASE_URL_EXISTS');\n  }\n  const now = Date.now();\n  const wireApi = normalizeWireApi(input.wireApi);\n  const provider: CodexModelProvider = {\n    id: createProviderId(),\n    name,\n    baseUrl,\n    sourceTag: sanitizeName(input.sourceTag ?? '') || undefined,\n    integrationType: normalizeIntegrationType(input.integrationType),\n    modelCatalog:\n      normalizeModelCatalog(input.modelCatalog) ??\n      presetModelCatalogForBaseUrl(baseUrl),\n    modelContextWindows: normalizeModelContextWindows(\n      input.modelContextWindows,\n      normalizeModelCatalog(input.modelCatalog) ??\n        presetModelCatalogForBaseUrl(baseUrl) ??\n        [],\n    ),","sourceCodeStart":571,"sourceCodeEnd":607,"githubUrl":"https://github.com/jlcodes99/cockpit-tools/blob/1ed8b77992d62ca81fabf744deb0839ad361d5bf/src/services/codexModelProviderService.ts#L571-L607","documentation":"createCodexModelProvider rejects a new provider whose baseUrl normalizes to the same value as an existing provider. The service enforces uniqueness of provider base URLs by comparing normalizeCodexModelProviderBaseUrl(item.baseUrl) across all stored providers. It is a deliberate duplicate-prevention guard, not a network or runtime failure.","triggerScenarios":"Calling createCodexModelProvider({ name, baseUrl, ... }) when any already-stored provider's baseUrl, after normalization (scheme/host/path canonicalization), equals the normalized form of the incoming baseUrl. Thrown at src/services/codexModelProviderService.ts:589 before any provider record is created.","commonSituations":"Re-running an init/import script that already created the provider; adding a second provider entry pointing at the same gateway (e.g. http://localhost:8080 vs localhost:8080/v1, which normalize to the same value); switching a proxy to a new port but leaving the old provider row in place; UI double-submit of the save form (handleSaveProvider).","solutions":["Search existing providers for one whose baseUrl matches (after normalization) and reuse/update it via updateCodexModelProvider instead of creating a new one.","Change the new provider's baseUrl to a distinct endpoint (different host, port, or path prefix).","If the old duplicate is stale, delete it first, then re-run createCodexModelProvider.","Wrap the call in try/catch for 'PROVIDER_BASE_URL_EXISTS' and fall back to an update path when the message matches."],"exampleFix":"// before: blind create, throws on second run\nawait createCodexModelProvider({ name: 'proxy', baseUrl: 'http://localhost:8080/v1' });\n// after: reuse or update the existing provider\nconst existing = (await listCodexModelProviders()).find(\n  (p) => normalizeCodexModelProviderBaseUrl(p.baseUrl) === normalizeCodexModelProviderBaseUrl('http://localhost:8080/v1'),\n);\nconst provider = existing\n  ? await updateCodexModelProvider(existing.id, { name: 'proxy' })\n  : await createCodexModelProvider({ name: 'proxy', baseUrl: 'http://localhost:8080/v1' });","handlingStrategy":"validation","validationCode":"import { listCodexModelProviders } from './codexModelProviderService';\n// approximate normalizer: compare trimmed, lower-cased URL without trailing slash\nconst norm = (u: string) => new URL(u).toString().replace(/\\/$/, '').toLowerCase();\nconst normalized = norm(baseUrl);\nconst exists = (await listCodexModelProviders()).some((p) => norm(p.baseUrl) === normalized);\nif (exists) throw new Error('PROVIDER_BASE_URL_EXISTS');","typeGuard":null,"tryCatchPattern":"try {\n  await createCodexModelProvider({ name, baseUrl });\n} catch (e) {\n  if ((e as Error).message === 'PROVIDER_BASE_URL_EXISTS') {\n    // fall back to locating and updating the existing provider\n  } else throw e;\n}","preventionTips":["Always list providers and compare normalized base URLs before creating a new one.","Make provider creation idempotent in scripts: check-then-create or upsert.","Debounce/disable the save button in UIs to prevent double submits.","Clean up stale provider rows during environment resets."],"tags":["validation","duplicate-resource","config"],"backgroundTag":"duplicate-resource-conflict","analyzedSha":"1ed8b77992d62ca81fabf744deb0839ad361d5bf","analyzedAt":"2026-09-05T09:51:41.178Z","contentChangedAt":"2026-09-05T09:51:41.178Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}