{"record":{"id":"60ef176d02caa83f","repo":"coleam00/Archon","slug":"api-key-must-not-be-empty","errorCode":null,"errorMessage":"API key must not be empty.","messagePattern":"API key must not be empty\\.","errorType":"validation","errorClass":"InvalidProviderKeyError","httpStatus":null,"severity":"error","filePath":"packages/core/src/credentials/connect-service.ts","lineNumber":62,"sourceCode":"}\n\n/**\n * Validate and store a user's API key for a credential vendor. Accepts legacy\n * agent-keyed ids (`claude`/`codex`/`copilot`) and stores under the\n * vendor-canonical id. Throws {@link InvalidProviderKeyError} (before any DB\n * write) when the key is blank or the vendor is not in the registry-derived\n * connectable catalog; any other throw is a storage failure. The plaintext key\n * is encrypted inside the store and is never logged.\n */\nexport async function persistProviderApiKey(\n  userId: string,\n  provider: string,\n  apiKey: string,\n  label?: string | null\n): Promise<PersistProviderApiKeyResult> {\n  const trimmedKey = apiKey.trim();\n  if (!trimmedKey) {\n    throw new InvalidProviderKeyError('API key must not be empty.');\n  }\n  const vendor = normalizeCredentialVendor(provider);\n  if (!isConnectableVendor(vendor)) {\n    throw new InvalidProviderKeyError(\n      `Unknown provider '${provider}'. Known: ${listConnectableVendors().join(', ')}.`\n    );\n  }\n  const normalizedLabel = label?.trim() || null;\n  await saveUserProviderKey({\n    userId,\n    provider: vendor,\n    kind: 'api_key',\n    apiKey: trimmedKey,\n    label: normalizedLabel,\n  });\n  // Never log the key value — vendor + user only.\n  getLog().info({ userId, provider: vendor }, 'provider_api_key.persisted');\n  return { provider: vendor, kind: 'api_key', label: normalizedLabel };","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/core/src/credentials/connect-service.ts#L44-L80","documentation":"InvalidProviderKeyError thrown by persistProviderApiKey when the submitted API key is empty or whitespace-only. The service trims the input and refuses to encrypt/persist an empty secret, since a stored empty key would be delivered to the provider and fail at call time with a confusing auth error.","triggerScenarios":"Calling persistProviderApiKey(userId, provider, apiKey) with apiKey = '' or a string of spaces — e.g. an empty form field, an env-var expansion that resolved to nothing, or a client submitting the connect form without pasting a key.","commonSituations":"Web UI connect form submitted with a blank key field; CI scripting that interpolates an unset secret variable into the connect call; copying a placeholder instead of the real key then trimming it away.","solutions":["Supply the actual API key string from the provider console before calling persistProviderApiKey.","Validate client-side that the key field is non-empty (trimmed) before submitting.","Check the source variable/secret in scripts — an unset env var often interpolates to an empty string."],"exampleFix":"// before\nawait persistProviderApiKey(userId, 'anthropic', process.env.MY_KEY ?? '');\n// after\nconst key = process.env.MY_KEY?.trim();\nif (!key) throw new Error('MY_KEY is not set');\nawait persistProviderApiKey(userId, 'anthropic', key);","handlingStrategy":"validation","validationCode":"function canPersistApiKey(apiKey: string): boolean {\n  return typeof apiKey === 'string' && apiKey.trim().length > 0;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await persistProviderApiKey(userId, provider, key);\n} catch (e) {\n  if (e instanceof InvalidProviderKeyError && e.message.includes('must not be empty')) {\n    // prompt user for the key again\n  } else throw e;\n}","preventionTips":["Require the key field in UI forms (required + minLength validation).","In scripts, fail on unset env vars: check before interpolating into the connect call.","Trim and non-empty check the key at every call site boundary.","Log a clear message when a secret source resolves to empty — never store placeholder values."],"tags":["credentials","api-key","validation"],"backgroundTag":"empty-api-key","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}