From 55fd4022e844ece6d0e06b1198ee93da87d4e6be Mon Sep 17 00:00:00 2001 From: A <258483684+la14-1@users.noreply.github.com> Date: Wed, 11 Feb 2026 06:26:19 -0800 Subject: [PATCH] fix: improve error messages with actionable guidance for common failures (#452) - Add signal exit code handling (130/Ctrl+C, 137/killed, 255/SSH failure, 2/syntax error) - Replace vague "Cloud API retry logic exhausted" with attempt count and retry advice - Add network troubleshooting hint to API network error after retries - Clarify OAuth fallback prompt: explain why OAuth failed and what happens next - Consolidate auth cancellation message with three clear recovery options Agent: ux-engineer Co-authored-by: A <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) --- cli/src/commands.ts | 16 ++++++++++++++++ shared/common.sh | 25 +++++++++++++------------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/cli/src/commands.ts b/cli/src/commands.ts index ed476cf4..7db53b17 100644 --- a/cli/src/commands.ts +++ b/cli/src/commands.ts @@ -424,6 +424,17 @@ function reportDownloadError(ghUrl: string, err: unknown): never { export function getScriptFailureGuidance(exitCode: number | null, cloud: string): string[] { switch (exitCode) { + case 130: + return ["Script was interrupted (Ctrl+C). No server was left running."]; + case 137: + return ["Script was killed (likely by the system due to timeout or out of memory)."]; + case 255: + return [ + "SSH connection failed. Common causes:", + " - Server is still booting (wait a moment and retry)", + " - Firewall blocking SSH port 22", + " - Server was terminated before the session started", + ]; case 127: return [ "A required command was not found. Check that these are installed:", @@ -432,6 +443,11 @@ export function getScriptFailureGuidance(exitCode: number | null, cloud: string) ]; case 126: return ["A command was found but could not be executed (permission denied)."]; + case 2: + return [ + "Shell syntax or argument error. This is likely a bug in the script.", + ` Report it at: ${pc.cyan(`https://github.com/OpenRouterTeam/spawn/issues`)}`, + ]; case 1: return [ "Common causes:", diff --git a/shared/common.sh b/shared/common.sh index 02a93abe..71f1b0cb 100644 --- a/shared/common.sh +++ b/shared/common.sh @@ -747,14 +747,13 @@ get_openrouter_api_key_oauth() { # OAuth failed, offer manual entry echo "" - log_warn "OAuth authentication was not completed." - log_info "You can enter your API key manually instead." - log_info "Get a free key at: https://openrouter.ai/settings/keys" + log_warn "Browser-based OAuth login was not completed (timed out or browser not available)." + log_info "You can paste an API key instead. Create one at: https://openrouter.ai/settings/keys" echo "" local manual_choice - manual_choice=$(safe_read "Would you like to enter your API key manually? (Y/n): ") || { + manual_choice=$(safe_read "Paste your API key manually? (Y/n): ") || { log_error "Cannot prompt for manual entry in non-interactive mode" - log_warn "Set OPENROUTER_API_KEY environment variable for non-interactive usage" + log_warn "Set OPENROUTER_API_KEY environment variable before running spawn" return 1 } @@ -763,12 +762,11 @@ get_openrouter_api_key_oauth() { echo "${api_key}" return 0 else - log_error "Authentication cancelled by user" - log_error "" - log_error "An OpenRouter API key is required to use spawn." - log_error "Get your free API key at: https://openrouter.ai/settings/keys" - log_error "" - log_error "For non-interactive usage, set: OPENROUTER_API_KEY=sk-or-v1-..." + log_error "Authentication cancelled. An OpenRouter API key is required to use spawn." + log_warn "To authenticate, either:" + log_warn " - Re-run this command and complete the OAuth flow in your browser" + log_warn " - Set OPENROUTER_API_KEY=sk-or-v1-... before running spawn" + log_warn " - Create a key at: https://openrouter.ai/settings/keys" return 1 fi } @@ -1112,6 +1110,7 @@ _handle_api_transient_error() { if [[ "${error_type}" == "network" ]]; then if ! _api_should_retry_on_error "network" "${attempt}" "${max_retries}" "${!interval_var}" "${!max_interval_var}" "Cloud API network error"; then log_error "Cloud API network error after ${max_retries} attempts" + log_warn "Check your internet connection and verify the provider's API is reachable." return 1 fi else @@ -1166,7 +1165,9 @@ _cloud_api_retry_loop() { return 0 done - log_error "Cloud API retry logic exhausted" + log_error "Cloud API request failed after ${max_retries} attempts" + log_warn "This is usually caused by rate limiting or temporary provider issues." + log_warn "Wait a minute and try again, or check the provider's status page." return 1 }