{"record":{"id":"714f50f7f91f1fd2","repo":"gitbutlerapp/gitbutler","slug":"enter-an-openai-api-key-ai","errorCode":null,"errorMessage":"Enter an OpenAI API key","messagePattern":"Enter an OpenAI API key","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-api/src/ai.rs","lineNumber":181,"sourceCode":"    value.and_then(|value| {\n        let value = value.trim();\n        (!value.is_empty()).then(|| value.to_string())\n    })\n}\n\nfn validate_update(\n    update: &AiConfigurationUpdate,\n    openai_has_key: bool,\n    anthropic_has_key: bool,\n) -> Result<()> {\n    let configuration = domain_configuration(update, DomainConfiguration::default())?;\n\n    if configuration.provider == LLMProviderKind::OpenAi\n        && configuration.openai.key_option == CredentialsKeyOption::BringYourOwn\n        && submitted_key(update.openai_api_key.clone()).is_none()\n        && !openai_has_key\n    {\n        bail!(\"Enter an OpenAI API key\")\n    }\n    if configuration.provider == LLMProviderKind::Anthropic\n        && configuration.anthropic.key_option == CredentialsKeyOption::BringYourOwn\n        && submitted_key(update.anthropic_api_key.clone()).is_none()\n        && !anthropic_has_key\n    {\n        bail!(\"Enter an Anthropic API key\")\n    }\n    Ok(())\n}\n\nfn domain_configuration(\n    update: &AiConfigurationUpdate,\n    mut configuration: DomainConfiguration,\n) -> Result<DomainConfiguration> {\n    configuration.provider = provider(&update.provider)?;\n    configuration.openai = OpenAiConfiguration {\n        key_option: key_option(\"OpenAI\", &update.openai_key_option)?,","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-api/src/ai.rs#L163-L199","documentation":"Validation error from `validate_update` (called by `update_ai_configuration`): an OpenAI provider configuration using the BringYourOwn credential option requires an API key, but none was submitted and none exists in storage. The update is rejected so the provider is never left without usable credentials.","triggerScenarios":"Calling the AI configuration update with provider=openai, openai.key_option=BringYourOwn, an empty/absent `openai_api_key` in the update payload, and no previously stored OpenAI key.","commonSituations":"Switching the key option to BringYourOwn without pasting a key; clearing the key field in the settings UI and saving; first-time setup where the key vault is empty.","solutions":["Provide a non-empty `openai_api_key` in the update request","Check the stored credential (an existing key satisfies the check — re-submit it if the field is blank by UI design)","Switch `openai.key_option` to a non-BringYourOwn credential source (e.g. environment-based key)","Confirm the key is not only whitespace; `submitted_key` rejects blank values"],"exampleFix":"// before\nconst update = { openai: { keyOption: \"BringYourOwn\" } };\nawait api.updateAiConfiguration(update);\n// after\nconst update = { openai: { keyOption: \"BringYourOwn\" }, openaiApiKey: key.trim() || undefined };\nif (!update.openaiApiKey && !(await api.hasStoredOpenAiKey())) {\n  throw new Error(\"OpenAI API key required\");\n}\nawait api.updateAiConfiguration(update);","handlingStrategy":"validation","validationCode":"if (cfg.provider === \"openai\" && cfg.openai.keyOption === \"BringYourOwn\" &&\n    !update.openaiApiKey?.trim() && !(await api.hasStoredOpenAiKey())) {\n  throw new Error(\"openai api key required before saving\");\n}","typeGuard":"function hasOpenAiKey(u: AiConfigUpdate): boolean {\n  return typeof u.openaiApiKey === \"string\" && u.openaiApiKey.trim().length > 0;\n}","tryCatchPattern":"try {\n  await api.updateAiConfiguration(update);\n} catch (e) {\n  if (String(e).includes(\"Enter an OpenAI API key\")) {\n    setFieldError(\"openaiApiKey\", \"required for BringYourOwn credential mode\");\n  } else { throw e; }\n}","preventionTips":["Make the API key field required in the form when keyOption=BringYourOwn and no key is stored","Validate on the client before submitting the update","Distinguish \"clear key\" intent from \"save without key\" intent in the UI","Trim submitted keys; blank strings do not count"],"tags":["validation","api-key","openai","configuration"],"backgroundTag":"missing-api-key","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}