CLI Development

CLI Error Handling

The CLI shows expected errors as clean messages without a stack trace, and shows the full traceback for unexpected bugs.

Error Classes (src/control_tower/utils/errors.py)

  • ExpectedError - Base class for all user-facing errors. These are caught at the CLI layer and displayed cleanly.
  • ValidationError - User provided conflicting options or invalid input.
  • StateError - Required precondition not met (missing resource, config, or setup).
  • OperationError - An external operation (subprocess, container command) failed.

Using Errors in CLI Commands

Apply the @handle_user_errors decorator immediately after @click.command() (or @group.command()). Decorators execute top-to-bottom during invocation, so placed lower in the stack it won't catch errors from the decorators above it:

from control_tower.cli.base import handle_user_errors from control_tower.utils.errors import StateError, ValidationError @click.command() @handle_user_errors # Must be right after @click.command() @click.option("--some-option", help="...") @other_decorators() def my_command(option_a: bool, option_b: bool): if option_a and option_b: raise ValidationError("`option_a` and `option_b` cannot both be set") if not some_precondition(): raise StateError("Context not configured. Run 'ct live up' first")

The decorator:

  1. Converts ExpectedError exceptions to click.ClickException for clean display
  2. Transforms parameter names in messages to CLI equivalents:
    • Backtick-wrapped names: `option_a` → --option-a
    • Underscore names: option_a → --option-a
  3. Adds a hidden --traceback flag that shows the full stack trace for any error

Guidelines

  • Use ExpectedError subclasses for errors the user can understand and potentially fix
  • Use standard exceptions (RuntimeError, ValueError, etc.) for unexpected cases that need stack traces
  • Wrap parameter names in backticks in error messages so they are converted to CLI names; names without an underscore are only converted when backticked
  • Keep error messages concise and actionable