{"record":{"id":"08a619eda4474e53","repo":"gitbutlerapp/gitbutler","slug":"enter-an-openai-api-key","errorCode":null,"errorMessage":"Enter an OpenAI API key","messagePattern":"Enter an OpenAI API key","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-napi/src/ai.rs","lineNumber":136,"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":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-napi/src/ai.rs#L118-L154","documentation":"Validation in the AI configuration update flow: when the active provider is OpenAI with the 'BringYourOwn' credential option, a usable key must exist — either submitted in this update or already stored for the project. If both are absent, `validate_update` rejects the update with this form-style message.","triggerScenarios":"An `AiConfigurationUpdate` with provider=openai and openai_key_option=bring-your-own, no non-empty `openai_api_key` in the payload (after trim), and no previously stored OpenAI key.","commonSituations":"First-time setup where the user selects bring-your-own but leaves the key field empty; the frontend submits the update before the key input state is populated; a previously stored key was cleared.","solutions":["Include a non-empty (after trim) `openai_api_key` in the same update","If a key is already stored, make sure the update does not clear it and resend the stored-key state","Switch the credential option away from bring-your-own if no key should be supplied"],"exampleFix":"// before\nawait updateAiConfig({ provider: 'openai', openaiKeyOption: 'bring-your-own' }); // bails\n\n// after: submit the key together with the option\nawait updateAiConfig({\n  provider: 'openai',\n  openaiKeyOption: 'bring-your-own',\n  openaiApiKey: keyInput.trim(), // must be non-empty\n});","handlingStrategy":"validation","validationCode":"// TypeScript: pre-validate the BYO key requirement client-side\nif (\n  update.provider === 'openai' &&\n  update.openaiKeyOption === 'bring-your-own' &&\n  !(update.openaiApiKey ?? '').trim() &&\n  !hasStoredOpenAiKey\n) {\n  showFieldError('openaiApiKey', 'Enter an OpenAI API key');\n  return;\n}\nawait updateAiConfig(update);","typeGuard":null,"tryCatchPattern":"Catch the NAPI error whose message is 'Enter an OpenAI API key' and attach it to the key input field; keep the user on the form instead of failing silently.","preventionTips":["Disable submit until the BYO key field is non-empty when that option is selected","Track whether a key is already stored so provider switches do not clear it implicitly"],"tags":["ai","openai","api-key","validation","napi"],"backgroundTag":"missing-api-key","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}