jlcodes99/cockpit-tools · error

API_KEY_EXISTS

API_KEY_EXISTS

Error message

API_KEY_EXISTS

What it means

Thrown during an API-key update when the new (sanitized) key value duplicates another key under the same provider, excluding the key being updated. The service enforces key uniqueness per provider to keep credential records distinct. The update is rejected before any mutation occurs.

Source

Thrown at src/services/codexModelProviderService.ts:826

export async function updateApiKeyOnCodexModelProvider(
  providerId: string,
  apiKeyId: string,
  apiKey: string,
  name?: string,
): Promise<CodexModelProvider> {
  const normalizedApiKey = sanitizeApiKey(apiKey);
  if (!normalizedApiKey) throw new Error("API_KEY_REQUIRED");

  const providers = await ensureProvidersLoaded();
  const provider = providers.find((item) => item.id === providerId);
  if (!provider) throw new Error("PROVIDER_NOT_FOUND");
  const existing = provider.apiKeys.find((item) => item.id === apiKeyId);
  if (!existing) throw new Error("API_KEY_NOT_FOUND");

  const duplicate = provider.apiKeys.some(
    (item) => item.id !== apiKeyId && sanitizeApiKey(item.apiKey) === normalizedApiKey,
  );
  if (duplicate) throw new Error("API_KEY_EXISTS");

  const now = Date.now();
  existing.apiKey = normalizedApiKey;
  if (name !== undefined) {
    existing.name = sanitizeName(name);
  }
  existing.updatedAt = now;
  provider.updatedAt = now;
  await writeProviders(providers);
  return { ...provider, apiKeys: provider.apiKeys.map((item) => ({ ...item })) };
}

export async function testCodexModelProviderConnection(input: {
  baseUrl: string;
  apiKey: string;
  wireApi?: CodexProviderWireApi | null;
}): Promise<CodexLocalAccessTestResult> {
  return await invoke('codex_test_model_provider_connection', {

View on GitHub (pinned to 1ed8b77992)

Solutions

  1. Use a distinct secret value that no other key under this provider stores.
  2. If the intent is to replace key B, delete key B first or update key B itself instead.
  3. Trim the input — if you only meant cosmetic changes (name), update the name field, not the key value.

Example fix

// before
await updateProviderApiKey(providerId, keyA.id, keyB.apiKey); // duplicate
// after
await deleteProviderApiKey(providerId, keyB.id);
await updateProviderApiKey(providerId, keyA.id, newSecret); // unique value
Defensive patterns

Strategy: validation

Validate before calling

const provider = await getCodexModelProvider(providerId);
const normalized = secret.trim();
if (provider.apiKeys.some(k => k.id !== apiKeyId && k.apiKey.trim() === normalized)) {
  throw new Error('another key under this provider already stores this secret');
}

Try / catch

try {
  await updateProviderApiKey(providerId, apiKeyId, secret);
} catch (e) {
  if (e.message === 'API_KEY_EXISTS') {
    console.error('Duplicate secret: delete the other key entry or supply a unique value.');
  } else throw e;
}

Prevention

When it happens

Trigger: Updating key A with the value of key B under the same provider (item.id !== apiKeyId and sanitized values equal); pasting the same secret into two key entries; whitespace-only differences that sanitizeApiKey collapses to the same value.

Common situations: Copy-paste of the same token twice into different named keys; rotating a key to a value already stored as another entry; attempts to 'rename' a key by updating another entry with the same secret.

Related errors


AI-assisted analysis of jlcodes99/cockpit-tools@1ed8b77992 (2026-09-05). Data as JSON: /api/errors/6cb8b5ec754fceac. Report an issue: GitHub.