-
Notifications
You must be signed in to change notification settings - Fork 1
Fix API key errors: propagate authentication failures as errors instead of warnings #76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
90cd57b
4343fb7
a5fa349
d44b395
5c1c5fa
ed0e474
667cc4c
5ee0507
a3b3ace
88cc4b6
e0d1e79
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,207 @@ | ||||||
| //! Error handling utilities for the git-ai CLI tool. | ||||||
| //! | ||||||
| //! This module provides helpers for detecting and handling specific error types, | ||||||
| //! particularly authentication failures from the OpenAI API. | ||||||
| //! | ||||||
| //! # OpenAI Error Structure | ||||||
| //! | ||||||
| //! According to the official async-openai documentation: | ||||||
| //! - `OpenAIError::ApiError(ApiError)` contains structured error information from OpenAI | ||||||
| //! - `ApiError` has fields: `message`, `type`, `param`, and `code` | ||||||
| //! - Authentication errors have `code` set to `"invalid_api_key"` | ||||||
| //! - `OpenAIError::Reqwest(Error)` contains HTTP-level errors (connection issues, etc.) | ||||||
| //! | ||||||
| //! Reference: https://docs.rs/async-openai/latest/async_openai/error/ | ||||||
|
|
||||||
| use anyhow::Error; | ||||||
| use async_openai::error::OpenAIError; | ||||||
|
|
||||||
| /// Checks if an error represents an OpenAI API authentication failure. | ||||||
| /// | ||||||
| /// This function detects authentication failures by checking for: | ||||||
| /// 1. **Structured API errors** (preferred): Checks if the error contains an `OpenAIError::ApiError` | ||||||
| /// with `code` field set to `"invalid_api_key"` - this is the official OpenAI error code | ||||||
| /// for authentication failures. | ||||||
| /// 2. **String-based fallback**: As a fallback, checks for authentication-related keywords in | ||||||
| /// the error message for cases where the error has been wrapped or converted to a string. | ||||||
| /// | ||||||
| /// This approach is based on the official OpenAI API error codes documentation and the | ||||||
| /// async-openai Rust library structure. | ||||||
| /// | ||||||
| /// # Arguments | ||||||
| /// | ||||||
| /// * `error` - The error to check | ||||||
| /// | ||||||
| /// # Returns | ||||||
| /// | ||||||
| /// `true` if the error appears to be an authentication failure, `false` otherwise | ||||||
| /// | ||||||
| /// # Examples | ||||||
| /// | ||||||
| /// ``` | ||||||
| /// use anyhow::anyhow; | ||||||
| /// use ai::error::is_openai_auth_error; | ||||||
| /// | ||||||
| /// let error = anyhow!("invalid_api_key: Incorrect API key provided"); | ||||||
| /// assert!(is_openai_auth_error(&error)); | ||||||
| /// ``` | ||||||
| pub fn is_openai_auth_error(error: &Error) -> bool { | ||||||
| // First, try to downcast to OpenAIError for accurate detection | ||||||
| if let Some(openai_err) = error.downcast_ref::<OpenAIError>() { | ||||||
| match openai_err { | ||||||
| // Official OpenAI API error with structured error code | ||||||
| OpenAIError::ApiError(api_err) => { | ||||||
| // Check for the official invalid_api_key error code | ||||||
| if api_err.code.as_deref() == Some("invalid_api_key") { | ||||||
| return true; | ||||||
| } | ||||||
| // Also check for authentication-related types | ||||||
| if let Some(err_type) = &api_err.r#type { | ||||||
| if err_type.contains("authentication") || err_type.contains("invalid_request_error") { | ||||||
| // For invalid_request_error, check if the message mentions API key | ||||||
| if err_type == "invalid_request_error" && api_err.message.to_lowercase().contains("api key") { | ||||||
| return true; | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| // HTTP-level errors (connection failures, malformed requests, etc.) | ||||||
| OpenAIError::Reqwest(_) => { | ||||||
| // Reqwest errors for auth issues typically manifest as connection errors | ||||||
| // when the API key format is completely invalid (e.g., "dl://BA7...") | ||||||
| let msg = error.to_string().to_lowercase(); | ||||||
| if msg.contains("error sending request") || msg.contains("connection") { | ||||||
|
||||||
| if msg.contains("error sending request") || msg.contains("connection") { | |
| if msg.contains("error sending request") || (msg.contains("connection") && msg.contains("openai")) { |
Copilot
AI
Oct 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This condition is too broad and may catch unrelated HTTP errors. Consider making it more specific by checking for OpenAI-related context or using a more precise pattern to avoid false positives with other services that might produce similar error messages.
| (msg.contains("http error") && msg.contains("error sending request")) | |
| (msg.contains("http error") && msg.contains("error sending request") && msg.contains("openai")) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,18 @@ | ||
| pub mod commit; | ||
| pub mod config; | ||
| pub mod hook; | ||
| pub mod style; | ||
| pub mod model; | ||
| pub mod debug_output; | ||
| pub mod error; | ||
| pub mod filesystem; | ||
| pub mod openai; | ||
| pub mod profiling; | ||
| pub mod function_calling; | ||
| pub mod generation; | ||
| pub mod hook; | ||
| pub mod model; | ||
| pub mod multi_step_analysis; | ||
| pub mod multi_step_integration; | ||
| pub mod openai; | ||
| pub mod profiling; | ||
| pub mod simple_multi_step; | ||
| pub mod debug_output; | ||
| pub mod generation; | ||
| pub mod style; | ||
|
|
||
| // Re-exports | ||
| pub use profiling::Profile; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,45 @@ | ||||||
| use ai::commit; | ||||||
| use ai::config::AppConfig; | ||||||
| use ai::model::Model; | ||||||
|
|
||||||
| #[tokio::test] | ||||||
| async fn test_invalid_api_key_propagates_error() { | ||||||
| // Initialize logging to capture warnings | ||||||
|
||||||
| // Initialize logging to capture warnings | |
| // Initialize logging to ensure errors are properly logged during authentication failure tests |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic has an issue: if
err_typecontains 'authentication', it should returntrueimmediately, but the current structure only returnstruefor theinvalid_request_errorcase. Authentication errors that don't match the exact string 'invalid_request_error' will fall through without being detected.