From fe774a3ec2e377d0f83df2a208b50e09b45f6fc3 Mon Sep 17 00:00:00 2001 From: overtrue Date: Wed, 8 Apr 2026 22:05:56 +0800 Subject: [PATCH 1/2] fix(formatter): classify invalid credentials as auth errors --- crates/cli/src/output/formatter.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/crates/cli/src/output/formatter.rs b/crates/cli/src/output/formatter.rs index a4b1849..a94610a 100644 --- a/crates/cli/src/output/formatter.rs +++ b/crates/cli/src/output/formatter.rs @@ -121,6 +121,15 @@ fn infer_error_metadata(message: &str) -> (&'static str, bool, Option) { return ("not_found", false, Some(NOT_FOUND_SUGGESTION.to_string())); } + if normalized.contains("access denied") + || normalized.contains("unauthorized") + || normalized.contains("forbidden") + || normalized.contains("authentication") + || normalized.contains("credentials") + { + return ("auth_error", false, Some(AUTH_SUGGESTION.to_string())); + } + if normalized.contains("invalid") || normalized.contains("cannot be empty") || normalized.contains("must be") @@ -131,15 +140,6 @@ fn infer_error_metadata(message: &str) -> (&'static str, bool, Option) { return ("usage_error", false, Some(USAGE_SUGGESTION.to_string())); } - if normalized.contains("access denied") - || normalized.contains("unauthorized") - || normalized.contains("forbidden") - || normalized.contains("authentication") - || normalized.contains("credentials") - { - return ("auth_error", false, Some(AUTH_SUGGESTION.to_string())); - } - if normalized.contains("already exists") || normalized.contains("conflict") || normalized.contains("precondition") @@ -509,6 +509,17 @@ mod tests { assert_eq!(details.suggestion.as_deref(), Some(USAGE_SUGGESTION)); } + #[test] + fn test_error_descriptor_from_message_prefers_auth_for_invalid_credentials() { + let descriptor = ErrorDescriptor::from_message("Invalid credentials for alias 'local'"); + let json = descriptor.to_json_output(); + + let details = json.details.expect("details should be present"); + assert_eq!(details.error_type, "auth_error"); + assert!(!details.retryable); + assert_eq!(details.suggestion.as_deref(), Some(AUTH_SUGGESTION)); + } + #[test] fn test_error_with_suggestion_overrides_default_hint() { let descriptor = ErrorDescriptor::from_code( From 60acfca982c544020fdcd04f02be08d6f007fcc6 Mon Sep 17 00:00:00 2001 From: overtrue Date: Sat, 11 Apr 2026 06:04:41 +0800 Subject: [PATCH 2/2] test(formatter): cover auth failure aliases --- crates/cli/src/output/formatter.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/cli/src/output/formatter.rs b/crates/cli/src/output/formatter.rs index a94610a..a10bbfc 100644 --- a/crates/cli/src/output/formatter.rs +++ b/crates/cli/src/output/formatter.rs @@ -520,6 +520,27 @@ mod tests { assert_eq!(details.suggestion.as_deref(), Some(AUTH_SUGGESTION)); } + #[test] + fn test_error_descriptor_from_message_classifies_other_auth_failures() { + for message in [ + "Unauthorized request for alias 'local'", + "Forbidden: bucket access denied", + "Authentication failed for alias 'local'", + ] { + let descriptor = ErrorDescriptor::from_message(message); + let json = descriptor.to_json_output(); + let details = json.details.expect("details should be present"); + + assert_eq!(details.error_type, "auth_error", "message: {message}"); + assert!(!details.retryable, "message: {message}"); + assert_eq!( + details.suggestion.as_deref(), + Some(AUTH_SUGGESTION), + "message: {message}" + ); + } + } + #[test] fn test_error_with_suggestion_overrides_default_hint() { let descriptor = ErrorDescriptor::from_code(