Merge pull request #12 from mantarayDigital/fix/start-sh-credentials-check

fix: Comprehensive authentication error handling
This commit is contained in:
Leon van Zyl
2026-01-10 11:29:13 +02:00
committed by GitHub
5 changed files with 187 additions and 38 deletions

View File

@@ -127,9 +127,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

View File

@@ -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."""
@@ -186,6 +227,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:
@@ -199,6 +243,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:
@@ -210,6 +266,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"

View File

@@ -9,6 +9,7 @@ Supports two paths for new projects:
""" """
import os import os
import re
import subprocess import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
@@ -24,6 +25,53 @@ from registry import (
register_project, 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: 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. 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. 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("\n" + "=" * 50)
print(" Project Specification Setup") print(" Project Specification Setup")
@@ -217,12 +266,25 @@ def run_spec_creation(project_dir: Path) -> bool:
try: try:
# Launch Claude Code with /create-spec command # Launch Claude Code with /create-spec command
# Project path included in command string so it populates $ARGUMENTS # 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}"], ["claude", f"/create-spec {project_dir}"],
check=False, # Don't raise on non-zero exit 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 # Check if spec was created in project prompts directory
if check_spec_exists(project_dir): if check_spec_exists(project_dir):
print("\n" + "-" * 50) print("\n" + "-" * 50)
@@ -232,6 +294,9 @@ def run_spec_creation(project_dir: Path) -> bool:
print("\n" + "-" * 50) print("\n" + "-" * 50)
print("Spec creation incomplete.") print("Spec creation incomplete.")
print(f"Please ensure app_spec.txt exists in: {get_project_prompts_dir(project_dir)}") 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 return False
except FileNotFoundError: 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: def run_agent(project_name: str, project_dir: Path) -> None:
"""Run the autonomous agent with the given project. """Run the autonomous agent with the given project.
Captures stderr to detect authentication errors and provide helpful guidance.
Args: Args:
project_name: Name of the project project_name: Name of the project
project_dir: Absolute path to the project directory 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 # Build the command - pass absolute path
cmd = [sys.executable, "autonomous_agent_demo.py", "--project-dir", str(project_dir.resolve())] 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: 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: except KeyboardInterrupt:
print("\n\nAgent interrupted. Run again to resume.") print("\n\nAgent interrupted. Run again to resume.")

View File

@@ -20,40 +20,18 @@ fi
echo "[OK] Claude CLI found" echo "[OK] Claude CLI found"
# Check if user has credentials # Note: Claude CLI no longer stores credentials in ~/.claude/.credentials.json
CLAUDE_CREDS="$HOME/.claude/.credentials.json" # We can't reliably check auth status without making an API call, so we just
if [ -f "$CLAUDE_CREDS" ]; then # verify the CLI is installed and remind the user to login if needed
echo "[OK] Claude credentials found" if [ -d "$HOME/.claude" ]; then
echo "[OK] Claude CLI directory found"
echo " (If you're not logged in, run: claude login)"
else else
echo "[!] Not authenticated with Claude" echo "[!] Claude CLI not configured"
echo "" echo ""
echo "You need to run 'claude login' to authenticate." echo "Please run 'claude login' to authenticate before continuing."
echo "This will open a browser window to sign in."
echo "" echo ""
read -p "Would you like to run 'claude login' now? (y/n): " LOGIN_CHOICE read -p "Press Enter to continue anyway, or Ctrl+C to exit..."
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
fi fi
echo "" echo ""

View File

@@ -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