Skip to content

[BUG-09] Command auto-import silently drops broken modules — registry missing commands with no CI signal #39

Description

@Wolfvin

Summary

scripts/commands/__init__.py auto-imports every *.py file in commands/ to trigger register_command(...) side-effects. If any module fails to import (syntax error, missing dependency, circular import), the exception is caught and logged at ERROR level — but the command is silently missing from the registry. CI does not catch this because test_integration.py only asserts len(COMMAND_REGISTRY) >= 41 (see BUG-04).

Evidence

scripts/commands/__init__.py:33-38:

for fname in sorted(os.listdir(_commands_dir)):
    if fname.endswith('.py') and fname != '__init__.py':
        try:
            importlib.import_module(f'.{fname[:-3]}', package='commands')
        except Exception as e:
            logging.getLogger('codelens').error(
                f"Failed to import command module '{fname}': {e}"
            )

No re-raise, no exit code change, no test assertion.

Impact

  1. Silent feature loss: A typo in commands/new_feature.py causes the feature to disappear from codelens --help with only a log line. If logging isn't configured (default level is WARNING per utils.py:22, but ERROR > WARNING so it should show — still easy to miss in CI noise), the user sees nothing.
  2. CI passes anyway: assert len(COMMAND_REGISTRY) >= 41 was set when there were 41 commands; with 60 commands now, 19 commands could fail to import before the test would notice.
  3. MCP server silently loses tools: mcp_server.py derives its tool list from COMMAND_REGISTRY. A broken command module means a missing MCP tool, with no signal to the agent.
  4. Hard to debug: The error log goes to stderr; MCP clients only see stdout (JSON-RPC), so they have no way to know a tool is missing.

Repro

# Introduce a syntax error in a command module
echo "def broken(" > scripts/commands/_test_broken.py
python3 scripts/codelens.py --help 2>&1 | grep -i "failed to import"
# Observe: error logged to stderr, _test_broken missing from --help, exit code 0.
rm scripts/commands/_test_broken.py

Suggested fix

Two layers:

  1. Fail-fast in dev / CI: Add an env var CODELENS_STRICT_COMMANDS=1 that, when set, re-raises the exception instead of logging. Set this in pytest.ini (via env=) and in .gitlab-ci.yml so CI catches import failures.
  2. Test coverage: Add a meta-test that asserts every *.py file in scripts/commands/ (excluding __init__.py) successfully imports and registers a command:
# tests/test_command_registry.py
import importlib, pkgutil, commands

def test_every_command_module_registers():
    cmd_dir = Path(commands.__file__).parent
    for f in cmd_dir.glob("*.py"):
        if f.name == "__init__.py": continue
        mod = importlib.import_module(f"commands.{f.stem}")
        # The module should have called register_command() at import time.
        # Verify by checking COMMAND_REGISTRY contains an entry whose
        # execute function lives in this module.
        registered = [
            name for name, info in commands.COMMAND_REGISTRY.items()
            if info["execute"].__module__ == mod.__name__
        ]
        assert registered, f"{f.name} did not register any command"
  1. Improve the log: Include the failed module name in a structured field that MCP clients can surface (e.g., emit a codelens.health.commands_failed metric).

Files

  • scripts/commands/__init__.py (L33-38)
  • tests/test_command_registry.py (new)
  • pytest.ini (add env= if using pytest-env, or document CODELENS_STRICT_COMMANDS=1 in CI)
  • .gitlab-ci.yml (set CODELENS_STRICT_COMMANDS=1 on test jobs)

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions