mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-01-30 06:12:06 +00:00
feat: Add authentication error handling to UI flow
Extend auth error detection to the web UI flow: server/main.py: - Fix setup_status() endpoint to check ~/.claude directory instead of non-existent .credentials.json file - Add explanatory comments about Claude CLI credential storage changes server/services/process_manager.py: - Add AUTH_ERROR_PATTERNS for detecting auth errors in agent output - Add is_auth_error() helper function - Add AUTH_ERROR_HELP message template - Update _stream_output() to detect auth errors in real-time - Buffer last 20 lines to catch auth errors on process exit - Broadcast clear help message to WebSocket clients when auth fails start_ui.sh: - Add Claude CLI installation check with helpful guidance - Add ~/.claude directory check with login reminder - Non-blocking warnings that don't prevent UI from starting This ensures users get clear, actionable feedback when authentication fails, whether using the CLI or the web UI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -120,9 +120,11 @@ async def setup_status():
|
|||||||
# Check for Claude CLI
|
# Check for Claude CLI
|
||||||
claude_cli = shutil.which("claude") is not None
|
claude_cli = shutil.which("claude") is not None
|
||||||
|
|
||||||
# Check for credentials file
|
# Check for Claude CLI configuration directory
|
||||||
credentials_path = Path.home() / ".claude" / ".credentials.json"
|
# Note: Claude CLI no longer stores credentials in ~/.claude/.credentials.json
|
||||||
credentials = credentials_path.exists()
|
# The existence of ~/.claude indicates the CLI has been configured
|
||||||
|
claude_dir = Path.home() / ".claude"
|
||||||
|
credentials = claude_dir.exists() and claude_dir.is_dir()
|
||||||
|
|
||||||
# Check for Node.js and npm
|
# Check for Node.js and npm
|
||||||
node = shutil.which("node") is not None
|
node = shutil.which("node") is not None
|
||||||
|
|||||||
@@ -36,6 +36,47 @@ SENSITIVE_PATTERNS = [
|
|||||||
r'aws[_-]?secret[=:][^\s]+',
|
r'aws[_-]?secret[=:][^\s]+',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Patterns that indicate Claude CLI authentication errors
|
||||||
|
AUTH_ERROR_PATTERNS = [
|
||||||
|
r"not\s+logged\s+in",
|
||||||
|
r"not\s+authenticated",
|
||||||
|
r"authentication\s+(failed|required|error)",
|
||||||
|
r"login\s+required",
|
||||||
|
r"please\s+(run\s+)?['\"]?claude\s+login",
|
||||||
|
r"unauthorized",
|
||||||
|
r"invalid\s+(token|credential|api.?key)",
|
||||||
|
r"expired\s+(token|session|credential)",
|
||||||
|
r"could\s+not\s+authenticate",
|
||||||
|
r"sign\s+in\s+(to|required)",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def is_auth_error(text: str) -> bool:
|
||||||
|
"""Check if text contains Claude CLI authentication error messages."""
|
||||||
|
if not text:
|
||||||
|
return False
|
||||||
|
text_lower = text.lower()
|
||||||
|
for pattern in AUTH_ERROR_PATTERNS:
|
||||||
|
if re.search(pattern, text_lower):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
AUTH_ERROR_HELP = """
|
||||||
|
================================================================================
|
||||||
|
AUTHENTICATION ERROR DETECTED
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Claude CLI requires authentication to work.
|
||||||
|
|
||||||
|
To fix this, run:
|
||||||
|
claude login
|
||||||
|
|
||||||
|
This will open a browser window to sign in.
|
||||||
|
After logging in, try starting the agent again.
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def sanitize_output(line: str) -> str:
|
def sanitize_output(line: str) -> str:
|
||||||
"""Remove sensitive information from output lines."""
|
"""Remove sensitive information from output lines."""
|
||||||
@@ -185,6 +226,9 @@ class AgentProcessManager:
|
|||||||
if not self.process or not self.process.stdout:
|
if not self.process or not self.process.stdout:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
auth_error_detected = False
|
||||||
|
output_buffer = [] # Buffer recent lines for auth error detection
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
while True:
|
while True:
|
||||||
@@ -198,6 +242,18 @@ class AgentProcessManager:
|
|||||||
decoded = line.decode("utf-8", errors="replace").rstrip()
|
decoded = line.decode("utf-8", errors="replace").rstrip()
|
||||||
sanitized = sanitize_output(decoded)
|
sanitized = sanitize_output(decoded)
|
||||||
|
|
||||||
|
# Buffer recent output for auth error detection
|
||||||
|
output_buffer.append(decoded)
|
||||||
|
if len(output_buffer) > 20:
|
||||||
|
output_buffer.pop(0)
|
||||||
|
|
||||||
|
# Check for auth errors
|
||||||
|
if not auth_error_detected and is_auth_error(decoded):
|
||||||
|
auth_error_detected = True
|
||||||
|
# Broadcast auth error help message
|
||||||
|
for help_line in AUTH_ERROR_HELP.strip().split('\n'):
|
||||||
|
await self._broadcast_output(help_line)
|
||||||
|
|
||||||
await self._broadcast_output(sanitized)
|
await self._broadcast_output(sanitized)
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
@@ -209,6 +265,12 @@ class AgentProcessManager:
|
|||||||
if self.process and self.process.poll() is not None:
|
if self.process and self.process.poll() is not None:
|
||||||
exit_code = self.process.returncode
|
exit_code = self.process.returncode
|
||||||
if exit_code != 0 and self.status == "running":
|
if exit_code != 0 and self.status == "running":
|
||||||
|
# Check buffered output for auth errors if we haven't detected one yet
|
||||||
|
if not auth_error_detected:
|
||||||
|
combined_output = '\n'.join(output_buffer)
|
||||||
|
if is_auth_error(combined_output):
|
||||||
|
for help_line in AUTH_ERROR_HELP.strip().split('\n'):
|
||||||
|
await self._broadcast_output(help_line)
|
||||||
self.status = "crashed"
|
self.status = "crashed"
|
||||||
elif self.status == "running":
|
elif self.status == "running":
|
||||||
self.status = "stopped"
|
self.status = "stopped"
|
||||||
|
|||||||
21
start_ui.sh
21
start_ui.sh
@@ -9,6 +9,27 @@ echo " AutoCoder UI"
|
|||||||
echo "===================================="
|
echo "===================================="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# Check if Claude CLI is installed
|
||||||
|
if ! command -v claude &> /dev/null; then
|
||||||
|
echo "[!] Claude CLI not found"
|
||||||
|
echo ""
|
||||||
|
echo " The agent requires Claude CLI to work."
|
||||||
|
echo " Install it from: https://claude.ai/download"
|
||||||
|
echo ""
|
||||||
|
echo " After installing, run: claude login"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo "[OK] Claude CLI found"
|
||||||
|
# Note: Claude CLI no longer stores credentials in ~/.claude/.credentials.json
|
||||||
|
# We can't reliably check auth status without making an API call
|
||||||
|
if [ -d "$HOME/.claude" ]; then
|
||||||
|
echo " (If you're not logged in, run: claude login)"
|
||||||
|
else
|
||||||
|
echo "[!] Claude CLI not configured - run 'claude login' first"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
# Check if Python is available
|
# Check if Python is available
|
||||||
if ! command -v python3 &> /dev/null; then
|
if ! command -v python3 &> /dev/null; then
|
||||||
if ! command -v python &> /dev/null; then
|
if ! command -v python &> /dev/null; then
|
||||||
|
|||||||
Reference in New Issue
Block a user