ux: improve error message clarity and formatting (#1133)

Enhance user-facing error messages with better structure and visual hierarchy:

**CLI Error Messages:**
- Add bold headers for "Next steps:" and "Possible causes:" sections
- Make action items more scannable and directive
- Simplify language (e.g., "temporarily" vs "temporarily unavailable")
- Reduce redundancy in network error messages

**Shell Error Messages:**
- Add color-coded section headers (yellow for "Common causes" and "Next steps")
- Apply syntax highlighting to commands with CYAN color
- Improve readability of multi-step installation instructions
- Use bullet points (•) instead of dashes for better visual scanning
- Add inline comments to commands (e.g., "# Check disk space")

**Impact:**
Users experiencing errors will:
- Find actionable steps faster with clear visual hierarchy
- Copy-paste commands more easily with syntax highlighting
- Understand root causes quicker with color-coded sections
- Have a better experience during failure scenarios

All changes maintain backward compatibility and work across bash 3.x (macOS) and modern bash.

Agent: ux-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
A 2026-02-14 14:44:47 -08:00 committed by GitHub
parent 21b8f612e5
commit 22cdd75f80
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 71 additions and 54 deletions

View file

@ -648,23 +648,25 @@ function reportDownloadFailure(primaryUrl: string, fallbackUrl: string, primaryS
console.error("\nThe spawn script doesn't exist at the expected location.");
console.error("\nThis usually means:");
console.error(" • The agent + cloud combination hasn't been implemented yet");
console.error(" • The script is currently being deployed (rare, but possible)");
console.error(" • The script is currently being deployed (rare)");
console.error(" • There's a temporary issue with the file server");
console.error(`\nWhat to do:`);
console.error(` 1. Check if it's marked as implemented: ${pc.cyan("spawn matrix")}`);
console.error(` 2. If the matrix shows it's available, wait 1-2 minutes and retry`);
console.error(` 3. Still not working? Report it: ${pc.cyan(`https://github.com/${REPO}/issues`)}`);
console.error(`\n${pc.bold("Next steps:")}`);
console.error(` 1. Verify it's implemented: ${pc.cyan("spawn matrix")}`);
console.error(` 2. If the matrix shows , wait 1-2 minutes and retry`);
console.error(` 3. Still broken? Report it: ${pc.cyan(`https://github.com/${REPO}/issues`)}`);
} else {
p.log.error(`Script download failed`);
console.error(`\nCouldn't download the spawn script (HTTP ${primaryStatus} from primary, ${fallbackStatus} from fallback).`);
if (primaryStatus >= 500 || fallbackStatus >= 500) {
console.error("\nThe servers are experiencing issues or are temporarily unavailable.");
console.error("\nThe servers are experiencing issues or temporarily unavailable.");
}
console.error(`\n${pc.bold("Next steps:")}`);
console.error(` 1. Check your internet connection`);
console.error(` 2. Wait a moment and try again`);
console.error(` 3. Check GitHub's status: ${pc.cyan("https://www.githubstatus.com")}`);
if (primaryStatus >= 500 || fallbackStatus >= 500) {
console.error(` 4. If GitHub is down, retry when it's back up`);
}
console.error(`\nWhat to do:`);
console.error(` 1. Verify your internet connection is working`);
console.error(` 2. Wait a minute and try again (servers may be recovering)`);
console.error(` 3. Check GitHub's status page: ${pc.cyan("https://www.githubstatus.com")}`);
console.error(` 4. If GitHub is down, wait and retry when it's back up`);
}
}
@ -676,30 +678,30 @@ function reportDownloadError(ghUrl: string, err: unknown): never {
const isTimeout = errMsg.toLowerCase().includes("timeout");
const isConnection = errMsg.toLowerCase().includes("connect") || errMsg.toLowerCase().includes("enotfound");
console.error("\nWhat's wrong:");
console.error(`\n${pc.bold("Possible causes:")}`);
if (isTimeout) {
console.error(" • Your internet connection is slow or unstable");
console.error(" • The download server isn't responding (possibly overloaded)");
console.error(" • A firewall may be slowing the connection");
console.error(" • Slow or unstable internet connection");
console.error(" • Download server not responding (possibly overloaded)");
console.error(" • Firewall blocking or slowing the connection");
} else if (isConnection) {
console.error(" • No internet connection detected");
console.error(" • A firewall or proxy is blocking GitHub access");
console.error(" • DNS isn't resolving GitHub's domain (check your DNS settings)");
console.error(" • No internet connection");
console.error(" • Firewall or proxy blocking GitHub access");
console.error(" • DNS not resolving GitHub's domain");
} else {
console.error(" • There's an issue with your internet connection");
console.error(" • GitHub's servers may be temporarily down");
console.error(" • Internet connection issue");
console.error(" • GitHub's servers temporarily down");
}
console.error("\nWhat to do:");
console.error(" 1. Check that your internet connection is working");
console.error(`\n${pc.bold("Next steps:")}`);
console.error(" 1. Check your internet connection");
if (isConnection) {
console.error(" 2. Test accessing github.com in your browser");
console.error(" 3. Check if a firewall or VPN is blocking access");
console.error(" 4. Try disabling any proxy settings temporarily");
console.error(" 2. Test github.com access in your browser");
console.error(" 3. Check firewall/VPN settings");
console.error(" 4. Try disabling proxy temporarily");
} else {
console.error(` 2. Verify this combination exists: ${pc.cyan("spawn matrix")}`);
console.error(" 3. Wait a minute and try again");
console.error(` 4. Test accessing the URL directly: ${pc.dim(ghUrl)}`);
console.error(` 2. Verify combination exists: ${pc.cyan("spawn matrix")}`);
console.error(" 3. Wait a moment and retry");
console.error(` 4. Test URL directly: ${pc.dim(ghUrl)}`);
}
process.exit(1);
}
@ -866,7 +868,7 @@ export function getScriptFailureGuidance(exitCode: number | null, cloud: string,
if (!entry) {
// Default/unknown exit code
return [
"Common causes:",
`${pc.bold("Common causes:")}`,
...credentialHints(cloud, authHint, "Missing"),
" - Cloud provider API rate limit or quota exceeded",
" - Missing local dependencies (SSH, curl, jq)",
@ -874,7 +876,7 @@ export function getScriptFailureGuidance(exitCode: number | null, cloud: string,
];
}
const lines = [entry.header, ...entry.lines];
const lines = [pc.bold(entry.header), ...entry.lines];
// Special handling for exit code 127 (missing command)
if (exitCode === 127) {

View file

@ -67,28 +67,29 @@ log_install_failed() {
local install_cmd="${2:-}"
local server_ip="${3:-}"
log_error "${agent_name} installation failed to complete successfully"
log_error "${agent_name} installation failed"
log_error ""
log_error "The agent could not be installed or verified on the server."
log_error ""
log_error "Common causes:"
log_error " - Network timeout downloading packages (npm, pip, etc.)"
log_error " - Insufficient disk space or memory on the server"
log_error " - Missing system dependencies for ${agent_name}"
log_error " - Cloud provider's package mirror is temporarily unavailable"
printf '%b\n' "${YELLOW}Common causes:${NC}" >&2
log_error " Network timeout downloading packages (npm, pip, etc.)"
log_error " Insufficient disk space or memory on the server"
log_error " Missing system dependencies for ${agent_name}"
log_error " • Cloud provider's package mirror temporarily unavailable"
log_error ""
log_error "Debugging steps:"
printf '%b\n' "${YELLOW}Next steps:${NC}" >&2
if [[ -n "${server_ip}" ]]; then
log_error " 1. SSH into the server and check logs:"
log_error " ssh root@${server_ip}"
log_error " Check: df -h (disk space)"
log_error " Check: free -h (memory)"
log_error " 1. SSH into the server to investigate:"
log_error " ${CYAN}ssh root@${server_ip}${NC}"
log_error " ${CYAN}df -h${NC} # Check disk space"
log_error " ${CYAN}free -h${NC} # Check memory"
fi
if [[ -n "${install_cmd}" ]]; then
log_error " 2. Try the installation manually:"
log_error " ${install_cmd}"
log_error " 2. Try manual installation:"
log_error " ${CYAN}${install_cmd}${NC}"
fi
log_error " 3. Re-run spawn to try on a fresh server (some failures are transient)"
log_error " 3. Retry with a fresh server (many failures are transient)"
log_error " ${CYAN}spawn <agent> <cloud>${NC}"
}
# ============================================================
@ -110,11 +111,18 @@ check_python_available() {
log_error ""
log_error "Spawn uses Python 3 for JSON parsing and API interactions."
log_error ""
log_error "Install Python 3:"
log_error " Ubuntu/Debian: sudo apt-get update && sudo apt-get install -y python3"
log_error " Fedora/RHEL: sudo dnf install -y python3"
log_error " macOS: brew install python3"
log_error " Arch Linux: sudo pacman -S python"
printf '%b\n' "${YELLOW}Install Python 3:${NC}" >&2
log_error " ${CYAN}# Ubuntu/Debian${NC}"
log_error " sudo apt-get update && sudo apt-get install -y python3"
log_error ""
log_error " ${CYAN}# Fedora/RHEL${NC}"
log_error " sudo dnf install -y python3"
log_error ""
log_error " ${CYAN}# macOS${NC}"
log_error " brew install python3"
log_error ""
log_error " ${CYAN}# Arch Linux${NC}"
log_error " sudo pacman -S python"
log_error ""
return 1
fi
@ -158,11 +166,18 @@ _install_jq_apk() {
_report_jq_not_found() {
log_error "jq is required but not installed"
log_error ""
log_error "Install jq for your system:"
log_error " Ubuntu/Debian: sudo apt-get install -y jq"
log_error " Fedora/RHEL: sudo dnf install -y jq"
log_error " macOS: brew install jq"
log_error " Other: https://jqlang.github.io/jq/download/"
printf '%b\n' "${YELLOW}Install jq for your system:${NC}" >&2
log_error " ${CYAN}# Ubuntu/Debian${NC}"
log_error " sudo apt-get install -y jq"
log_error ""
log_error " ${CYAN}# Fedora/RHEL${NC}"
log_error " sudo dnf install -y jq"
log_error ""
log_error " ${CYAN}# macOS${NC}"
log_error " brew install jq"
log_error ""
log_error " ${CYAN}# Other systems${NC}"
log_error " https://jqlang.github.io/jq/download/"
}
ensure_jq() {