From 1cbfeda889dee3fa0df0b9ec0b08b8445fc52784 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 11 Jun 2025 20:32:24 -0400 Subject: [PATCH 1/2] Improve user experience when API authentication fails Previously, the error message is shown as a stack trace, which is a lot extra information to process. In addition, it refers to an API needing a token, but doesn't mention which one is expected and that it's pulled from the environment. This does two things: - Update the CLI to rescue the authentication error, and re-raise as a Thor::Error. These are shown as text, and cause the CLI to exit with a non-zero exit code. - Add more details about which API provider is expected and which environment variable is expected. --- lib/roast.rb | 6 +++++- lib/roast/workflow/api_configuration.rb | 25 ++++++++++++++++++++-- lib/roast/workflow/configuration.rb | 2 +- lib/roast/workflow/workflow_initializer.rb | 2 +- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/roast.rb b/lib/roast.rb index 848089c7..0c1a64ff 100644 --- a/lib/roast.rb +++ b/lib/roast.rb @@ -64,7 +64,11 @@ def execute(*paths) raise Thor::Error, "Expected a Roast workflow configuration file, got directory: #{expanded_workflow_path}" if File.directory?(expanded_workflow_path) - Roast::Workflow::ConfigurationParser.new(expanded_workflow_path, files, options.transform_keys(&:to_sym)).begin! + begin + Roast::Workflow::ConfigurationParser.new(expanded_workflow_path, files, options.transform_keys(&:to_sym)).begin! + rescue Roast::Errors::AuthenticationError => e + raise Thor::Error, "API authentication failed: #{e.message}" + end end desc "version", "Display the current version of Roast" diff --git a/lib/roast/workflow/api_configuration.rb b/lib/roast/workflow/api_configuration.rb index 0ea4436a..0a9b4b04 100644 --- a/lib/roast/workflow/api_configuration.rb +++ b/lib/roast/workflow/api_configuration.rb @@ -29,6 +29,23 @@ def effective_token @api_token || environment_token end + # A reason for why the API authentication failed, if it failed. + # Includes the API provider and the environment variable, + # and whether it was present and invalid or missing + def authentication_failure_reason(e) + return unless e + + reason = "#{@api_provider.inspect} provider requires" + reason += " the #{environment_key} environment variable" if environment_key + reason += if environment_token.present? + " to be valid, but failed to authenticate" + else + " to be present, but not found" + end + + reason + end + private def process_api_configuration @@ -54,10 +71,14 @@ def extract_uri_base end def environment_token + ENV[environment_key] if environment_key + end + + def environment_key if openai? - ENV["OPENAI_API_KEY"] + "OPENAI_API_KEY" elsif openrouter? - ENV["OPENROUTER_API_KEY"] + "OPENROUTER_API_KEY" end end end diff --git a/lib/roast/workflow/configuration.rb b/lib/roast/workflow/configuration.rb index 0b77d92f..59dbc4e0 100644 --- a/lib/roast/workflow/configuration.rb +++ b/lib/roast/workflow/configuration.rb @@ -10,7 +10,7 @@ class Configuration attr_reader :config_hash, :workflow_path, :name, :steps, :pre_processing, :post_processing, :tools, :tool_configs, :mcp_tools, :function_configs, :model, :resource attr_accessor :target - delegate :api_provider, :openrouter?, :openai?, :uri_base, to: :api_configuration + delegate :api_provider, :openrouter?, :openai?, :uri_base, :authentication_failure_reason, to: :api_configuration # Delegate api_token to effective_token for backward compatibility def api_token diff --git a/lib/roast/workflow/workflow_initializer.rb b/lib/roast/workflow/workflow_initializer.rb index e0bea725..e22b9126 100644 --- a/lib/roast/workflow/workflow_initializer.rb +++ b/lib/roast/workflow/workflow_initializer.rb @@ -96,7 +96,7 @@ def configure_api_client # Validate the client configuration by making a test API call validate_api_client(client) if client rescue OpenRouter::ConfigurationError, Faraday::UnauthorizedError => e - error = Roast::Errors::AuthenticationError.new("API authentication failed: No API token provided or token is invalid") + error = Roast::Errors::AuthenticationError.new(@configuration.authentication_failure_reason(e)) error.set_backtrace(e.backtrace) ActiveSupport::Notifications.instrument("roast.workflow.start.error", { From 890b7dccfdb6ef8f98f066c0e77da7f157b8fd85 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 11 Jun 2025 20:44:09 -0400 Subject: [PATCH 2/2] Update test --- test/roast/workflow/workflow_initializer_test.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/roast/workflow/workflow_initializer_test.rb b/test/roast/workflow/workflow_initializer_test.rb index b8da889e..58bcdb07 100644 --- a/test/roast/workflow/workflow_initializer_test.rb +++ b/test/roast/workflow/workflow_initializer_test.rb @@ -216,6 +216,8 @@ def test_skips_configuration_for_unsupported_api_provider_when_no_token def test_raises_authentication_error_when_api_token_invalid @configuration.stubs(:api_token).returns("invalid-token") @configuration.stubs(:api_provider).returns(:openai) + @configuration.stubs(:openai?).returns(true) + @configuration.stubs(:openrouter?).returns(false) # Stub Raix configuration to indicate no client is configured yet Raix.configuration.stubs(:openai_client).returns(nil) @@ -232,7 +234,7 @@ def test_raises_authentication_error_when_api_token_invalid "roast.workflow.start.error", has_entries( error: "Roast::Errors::AuthenticationError", - message: "API authentication failed: No API token provided or token is invalid", + message: ":openai provider requires the OPENAI_API_KEY environment variable to be present, but not found", ), ).once @@ -240,12 +242,14 @@ def test_raises_authentication_error_when_api_token_invalid @initializer.setup end - assert_equal("API authentication failed: No API token provided or token is invalid", error.message) + assert_equal(":openai provider requires the OPENAI_API_KEY environment variable to be present, but not found", error.message) end def test_handles_openrouter_configuration_error @configuration.stubs(:api_token).returns("invalid-format-token") @configuration.stubs(:api_provider).returns(:openrouter) + @configuration.stubs(:openrouter?).returns(true) + @configuration.stubs(:openai?).returns(false) # Stub Raix configuration to indicate no client is configured yet Raix.configuration.stubs(:openrouter_client).returns(nil) @@ -257,7 +261,7 @@ def test_handles_openrouter_configuration_error "roast.workflow.start.error", has_entries( error: "Roast::Errors::AuthenticationError", - message: "API authentication failed: No API token provided or token is invalid", + message: ":openrouter provider requires the OPENROUTER_API_KEY environment variable to be present, but not found", ), ).once @@ -265,6 +269,6 @@ def test_handles_openrouter_configuration_error @initializer.setup end - assert_equal("API authentication failed: No API token provided or token is invalid", error.message) + assert_equal(":openrouter provider requires the OPENROUTER_API_KEY environment variable to be present, but not found", error.message) end end