diff --git a/server/main.py b/server/main.py index e717a53..586103b 100644 --- a/server/main.py +++ b/server/main.py @@ -127,9 +127,11 @@ async def setup_status(): # Check for Claude CLI claude_cli = shutil.which("claude") is not None - # Check for credentials file - credentials_path = Path.home() / ".claude" / ".credentials.json" - credentials = credentials_path.exists() + # Check for Claude CLI configuration directory + # Note: Claude CLI no longer stores credentials in ~/.claude/.credentials.json + # 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 node = shutil.which("node") is not None diff --git a/server/services/process_manager.py b/server/services/process_manager.py index 88ec2bd..9eff631 100644 --- a/server/services/process_manager.py +++ b/server/services/process_manager.py @@ -36,6 +36,47 @@ SENSITIVE_PATTERNS = [ 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: """Remove sensitive information from output lines.""" @@ -186,6 +227,9 @@ class AgentProcessManager: if not self.process or not self.process.stdout: return + auth_error_detected = False + output_buffer = [] # Buffer recent lines for auth error detection + try: loop = asyncio.get_running_loop() while True: @@ -199,6 +243,18 @@ class AgentProcessManager: decoded = line.decode("utf-8", errors="replace").rstrip() 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) except asyncio.CancelledError: @@ -210,6 +266,12 @@ class AgentProcessManager: if self.process and self.process.poll() is not None: exit_code = self.process.returncode 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" elif self.status == "running": self.status = "stopped" diff --git a/start.py b/start.py index 0236122..455bc97 100644 --- a/start.py +++ b/start.py @@ -9,6 +9,7 @@ Supports two paths for new projects: """ import os +import re import subprocess import sys from pathlib import Path @@ -24,6 +25,53 @@ from registry import ( register_project, ) +# 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(output: str) -> bool: + """ + Check if output contains Claude CLI authentication error messages. + + Args: + output: Combined stdout/stderr from subprocess + + Returns: + True if authentication error detected, False otherwise + """ + if not output: + return False + + output_lower = output.lower() + for pattern in AUTH_ERROR_PATTERNS: + if re.search(pattern, output_lower): + return True + return False + + +def print_auth_error_help() -> None: + """Print helpful message when authentication error is detected.""" + print("\n" + "=" * 50) + print(" Authentication Error Detected") + print("=" * 50) + print("\nClaude CLI requires authentication to work.") + print("\nTo fix this, run:") + print(" claude login") + print("\nThis will open a browser window to sign in.") + print("After logging in, try running this command again.") + print("=" * 50 + "\n") + def check_spec_exists(project_dir: Path) -> bool: """ @@ -203,6 +251,7 @@ def run_spec_creation(project_dir: Path) -> bool: Run Claude Code with /create-spec command to create project specification. The project path is passed as an argument so create-spec knows where to write files. + Captures stderr to detect authentication errors and provide helpful guidance. """ print("\n" + "=" * 50) print(" Project Specification Setup") @@ -217,12 +266,25 @@ def run_spec_creation(project_dir: Path) -> bool: try: # Launch Claude Code with /create-spec command # Project path included in command string so it populates $ARGUMENTS - subprocess.run( + # Capture stderr to detect auth errors while letting stdout flow to terminal + result = subprocess.run( ["claude", f"/create-spec {project_dir}"], check=False, # Don't raise on non-zero exit - cwd=str(Path(__file__).parent) # Run from project root + cwd=str(Path(__file__).parent), # Run from project root + stderr=subprocess.PIPE, + text=True ) + # Check for authentication errors in stderr + stderr_output = result.stderr or "" + if result.returncode != 0 and is_auth_error(stderr_output): + print_auth_error_help() + return False + + # If there was stderr output but not an auth error, show it + if stderr_output.strip() and result.returncode != 0: + print(f"\nClaude CLI error: {stderr_output.strip()}") + # Check if spec was created in project prompts directory if check_spec_exists(project_dir): print("\n" + "-" * 50) @@ -232,6 +294,9 @@ def run_spec_creation(project_dir: Path) -> bool: print("\n" + "-" * 50) print("Spec creation incomplete.") print(f"Please ensure app_spec.txt exists in: {get_project_prompts_dir(project_dir)}") + # If failed with non-zero exit and no spec, might be auth issue + if result.returncode != 0: + print("\nIf you're having authentication issues, try running: claude login") return False except FileNotFoundError: @@ -348,6 +413,8 @@ def create_new_project_flow() -> tuple[str, Path] | None: def run_agent(project_name: str, project_dir: Path) -> None: """Run the autonomous agent with the given project. + Captures stderr to detect authentication errors and provide helpful guidance. + Args: project_name: Name of the project project_dir: Absolute path to the project directory @@ -367,9 +434,28 @@ def run_agent(project_name: str, project_dir: Path) -> None: # Build the command - pass absolute path cmd = [sys.executable, "autonomous_agent_demo.py", "--project-dir", str(project_dir.resolve())] - # Run the agent + # Run the agent with stderr capture to detect auth errors + # stdout goes directly to terminal for real-time output try: - subprocess.run(cmd, check=False) + result = subprocess.run( + cmd, + check=False, + stderr=subprocess.PIPE, + text=True + ) + + # Check for authentication errors + stderr_output = result.stderr or "" + if result.returncode != 0: + if is_auth_error(stderr_output): + print_auth_error_help() + elif stderr_output.strip(): + # Show any other errors + print(f"\nAgent error:\n{stderr_output.strip()}") + # Still hint about auth if exit was unexpected + if "error" in stderr_output.lower() or "exception" in stderr_output.lower(): + print("\nIf this is an authentication issue, try running: claude login") + except KeyboardInterrupt: print("\n\nAgent interrupted. Run again to resume.") diff --git a/start.sh b/start.sh index 666a11a..88eaa8e 100644 --- a/start.sh +++ b/start.sh @@ -20,40 +20,18 @@ fi echo "[OK] Claude CLI found" -# Check if user has credentials -CLAUDE_CREDS="$HOME/.claude/.credentials.json" -if [ -f "$CLAUDE_CREDS" ]; then - echo "[OK] Claude credentials found" +# Note: Claude CLI no longer stores credentials in ~/.claude/.credentials.json +# We can't reliably check auth status without making an API call, so we just +# verify the CLI is installed and remind the user to login if needed +if [ -d "$HOME/.claude" ]; then + echo "[OK] Claude CLI directory found" + echo " (If you're not logged in, run: claude login)" else - echo "[!] Not authenticated with Claude" + echo "[!] Claude CLI not configured" echo "" - echo "You need to run 'claude login' to authenticate." - echo "This will open a browser window to sign in." + echo "Please run 'claude login' to authenticate before continuing." echo "" - read -p "Would you like to run 'claude login' now? (y/n): " LOGIN_CHOICE - - if [[ "$LOGIN_CHOICE" =~ ^[Yy]$ ]]; then - echo "" - echo "Running 'claude login'..." - echo "Complete the login in your browser, then return here." - echo "" - claude login - - # Check if login succeeded - if [ -f "$CLAUDE_CREDS" ]; then - echo "" - echo "[OK] Login successful!" - else - echo "" - echo "[ERROR] Login failed or was cancelled." - echo "Please try again." - exit 1 - fi - else - echo "" - echo "Please run 'claude login' manually, then try again." - exit 1 - fi + read -p "Press Enter to continue anyway, or Ctrl+C to exit..." fi echo "" diff --git a/start_ui.sh b/start_ui.sh index db3d6fa..a95cd8a 100644 --- a/start_ui.sh +++ b/start_ui.sh @@ -9,6 +9,27 @@ echo " AutoCoder UI" 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 if ! command -v python3 &> /dev/null; then if ! command -v python &> /dev/null; then