From 31519c2a10ab420ea355316ac6614c167b7d1e37 Mon Sep 17 00:00:00 2001 From: StillKnotKnown <192589389+StillKnotKnown@users.noreply.github.com> Date: Wed, 7 Jan 2026 08:19:24 +0200 Subject: [PATCH] fix: add helpful error message when Python dependencies are missing (ACS-145) (#755) * fix: add helpful error message when Python dependencies are missing When running runner scripts (spec_runner, insights_runner, etc.) without the virtual environment activated, users would get a cryptic ModuleNotFoundError for 'dotenv' or other dependencies. This fix adds a try-except around the dotenv import that provides a clear error message explaining: - The issue is likely due to not using the virtual environment - How to activate the venv (Linux/macOS/Windows) - How to install dependencies directly - Shows the current Python executable being used Also fixes CLI-USAGE.md which had incorrect paths for spec_runner.py (the file is in runners/, not the backend root). Related to: ACS-145 Signed-off-by: StillKnotKnown * fix: improve error messages with explicit package name and requirements path - cli/utils.py: Explicitly mention 'python-dotenv' and add 'pip install python-dotenv' option - insights_runner.py: Use full path 'apps/backend/requirements.txt' for clarity Signed-off-by: StillKnotKnown * refactor: centralize dotenv import error handling - Create shared import_dotenv() function in cli/utils.py - Update all runner scripts to use centralized function - Removes ~73 lines of duplicate code across 6 files - Ensures consistent error messaging (mentions python-dotenv explicitly) - Fixes path inconsistency in insights_runner.py Addresses CodeRabbit feedback about DRY principle violations. Signed-off-by: StillKnotKnown * style: fix import ordering to satisfy ruff I001 rule Add blank lines to separate local imports and function calls from third-party imports, properly delineating import groups. Signed-off-by: StillKnotKnown * style: auto-fix ruff I001 import ordering Ruff auto-fixed by adding blank line after 'from cli.utils import import_dotenv' to properly separate the import from the function call. Signed-off-by: StillKnotKnown * style: apply ruff formatting to cli/utils.py - Add blank line after import statement - Use double quotes instead of single quotes Signed-off-by: StillKnotKnown * refactor: return load_dotenv instead of mutating sys.modules - Change import_dotenv() to return load_dotenv callable - Remove sys.modules mutation for cleaner approach - Update callers to do: load_dotenv = import_dotenv() - Fixes ruff I001 import ordering violations - Preserves same error message on ImportError Addresses CodeRabbit feedback about import-order complexity. Signed-off-by: StillKnotKnown --------- Signed-off-by: StillKnotKnown Co-authored-by: StillKnotKnown Co-authored-by: Alex <63423455+AlexMadera@users.noreply.github.com> --- apps/backend/cli/utils.py | 41 ++++++++++++++++++++++++- apps/backend/runners/github/runner.py | 6 ++-- apps/backend/runners/gitlab/runner.py | 6 ++-- apps/backend/runners/ideation_runner.py | 6 ++-- apps/backend/runners/insights_runner.py | 6 ++-- apps/backend/runners/roadmap_runner.py | 6 ++-- apps/backend/runners/spec_runner.py | 16 +++++----- guides/CLI-USAGE.md | 8 ++--- 8 files changed, 73 insertions(+), 22 deletions(-) diff --git a/apps/backend/cli/utils.py b/apps/backend/cli/utils.py index 25b92ca2..b9969020 100644 --- a/apps/backend/cli/utils.py +++ b/apps/backend/cli/utils.py @@ -15,7 +15,46 @@ if str(_PARENT_DIR) not in sys.path: sys.path.insert(0, str(_PARENT_DIR)) from core.auth import get_auth_token, get_auth_token_source -from dotenv import load_dotenv + + +def import_dotenv(): + """ + Import and return load_dotenv with helpful error message if not installed. + + This centralized function ensures consistent error messaging across all + runner scripts when python-dotenv is not available. + + Returns: + The load_dotenv function + + Raises: + SystemExit: If dotenv cannot be imported, with helpful installation instructions. + """ + try: + from dotenv import load_dotenv as _load_dotenv + + return _load_dotenv + except ImportError: + sys.exit( + "Error: Required Python package 'python-dotenv' is not installed.\n" + "\n" + "This usually means you're not using the virtual environment.\n" + "\n" + "To fix this:\n" + "1. From the 'apps/backend/' directory, activate the venv:\n" + " source .venv/bin/activate # Linux/macOS\n" + " .venv\\Scripts\\activate # Windows\n" + "\n" + "2. Or install dependencies directly:\n" + " pip install python-dotenv\n" + " pip install -r requirements.txt\n" + "\n" + f"Current Python: {sys.executable}\n" + ) + + +# Load .env with helpful error if dependencies not installed +load_dotenv = import_dotenv() from graphiti_config import get_graphiti_status from linear_integration import LinearManager from linear_updater import is_linear_enabled diff --git a/apps/backend/runners/github/runner.py b/apps/backend/runners/github/runner.py index 669030e4..b3934cdc 100644 --- a/apps/backend/runners/github/runner.py +++ b/apps/backend/runners/github/runner.py @@ -56,8 +56,10 @@ if sys.platform == "win32": # Add backend to path sys.path.insert(0, str(Path(__file__).parent.parent.parent)) -# Load .env file -from dotenv import load_dotenv +# Load .env file with centralized error handling +from cli.utils import import_dotenv + +load_dotenv = import_dotenv() env_file = Path(__file__).parent.parent.parent / ".env" if env_file.exists(): diff --git a/apps/backend/runners/gitlab/runner.py b/apps/backend/runners/gitlab/runner.py index c2a0be32..d4f61827 100644 --- a/apps/backend/runners/gitlab/runner.py +++ b/apps/backend/runners/gitlab/runner.py @@ -26,8 +26,10 @@ from pathlib import Path # Add backend to path sys.path.insert(0, str(Path(__file__).parent.parent.parent)) -# Load .env file -from dotenv import load_dotenv +# Load .env file with centralized error handling +from cli.utils import import_dotenv + +load_dotenv = import_dotenv() env_file = Path(__file__).parent.parent.parent / ".env" if env_file.exists(): diff --git a/apps/backend/runners/ideation_runner.py b/apps/backend/runners/ideation_runner.py index 4e74f683..9b914456 100644 --- a/apps/backend/runners/ideation_runner.py +++ b/apps/backend/runners/ideation_runner.py @@ -26,8 +26,10 @@ from pathlib import Path # Add auto-claude to path sys.path.insert(0, str(Path(__file__).parent.parent)) -# Load .env file from auto-claude/ directory -from dotenv import load_dotenv +# Load .env file with centralized error handling +from cli.utils import import_dotenv + +load_dotenv = import_dotenv() env_file = Path(__file__).parent.parent / ".env" if env_file.exists(): diff --git a/apps/backend/runners/insights_runner.py b/apps/backend/runners/insights_runner.py index 22c2d9ad..bd4bf362 100644 --- a/apps/backend/runners/insights_runner.py +++ b/apps/backend/runners/insights_runner.py @@ -15,8 +15,10 @@ from pathlib import Path # Add auto-claude to path sys.path.insert(0, str(Path(__file__).parent.parent)) -# Load .env file from auto-claude/ directory -from dotenv import load_dotenv +# Load .env file with centralized error handling +from cli.utils import import_dotenv + +load_dotenv = import_dotenv() env_file = Path(__file__).parent.parent / ".env" if env_file.exists(): diff --git a/apps/backend/runners/roadmap_runner.py b/apps/backend/runners/roadmap_runner.py index 4a31c025..06625add 100644 --- a/apps/backend/runners/roadmap_runner.py +++ b/apps/backend/runners/roadmap_runner.py @@ -20,8 +20,10 @@ from pathlib import Path # Add auto-claude to path sys.path.insert(0, str(Path(__file__).parent.parent)) -# Load .env file from auto-claude/ directory -from dotenv import load_dotenv +# Load .env file with centralized error handling +from cli.utils import import_dotenv + +load_dotenv = import_dotenv() env_file = Path(__file__).parent.parent / ".env" if env_file.exists(): diff --git a/apps/backend/runners/spec_runner.py b/apps/backend/runners/spec_runner.py index 0bda6db1..4c5a4bba 100644 --- a/apps/backend/runners/spec_runner.py +++ b/apps/backend/runners/spec_runner.py @@ -26,11 +26,11 @@ The AI considers: - Risk factors and edge cases Usage: - python auto-claude/spec_runner.py --task "Add user authentication" - python auto-claude/spec_runner.py --interactive - python auto-claude/spec_runner.py --continue 001-feature - python auto-claude/spec_runner.py --task "Fix button color" --complexity simple - python auto-claude/spec_runner.py --task "Simple fix" --no-ai-assessment + python runners/spec_runner.py --task "Add user authentication" + python runners/spec_runner.py --interactive + python runners/spec_runner.py --continue 001-feature + python runners/spec_runner.py --task "Fix button color" --complexity simple + python runners/spec_runner.py --task "Simple fix" --no-ai-assessment """ import sys @@ -81,8 +81,10 @@ if sys.platform == "win32": # Add auto-claude to path (parent of runners/) sys.path.insert(0, str(Path(__file__).parent.parent)) -# Load .env file -from dotenv import load_dotenv +# Load .env file with centralized error handling +from cli.utils import import_dotenv + +load_dotenv = import_dotenv() env_file = Path(__file__).parent.parent / ".env" dev_env_file = Path(__file__).parent.parent.parent / "dev" / "auto-claude" / ".env" diff --git a/guides/CLI-USAGE.md b/guides/CLI-USAGE.md index c0c50785..dd41f507 100644 --- a/guides/CLI-USAGE.md +++ b/guides/CLI-USAGE.md @@ -74,16 +74,16 @@ All commands below should be run from the `apps/backend/` directory: source .venv/bin/activate # Create a spec interactively -python spec_runner.py --interactive +python runners/spec_runner.py --interactive # Or with a task description -python spec_runner.py --task "Add user authentication with OAuth" +python runners/spec_runner.py --task "Add user authentication with OAuth" # Force a specific complexity level -python spec_runner.py --task "Fix button color" --complexity simple +python runners/spec_runner.py --task "Fix button color" --complexity simple # Continue an interrupted spec -python spec_runner.py --continue 001-feature +python runners/spec_runner.py --continue 001-feature ``` ### Complexity Tiers