CLI Development
CLI Error Handling
The CLI uses a structured error system to provide clean user-facing error messages without stack traces for expected errors, while still showing full tracebacks for unexpected bugs.
Debugging Errors
Commands decorated with @handle_user_errors support a hidden --traceback flag that shows the full stack trace for any error.
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 to Click commands immediately after @click.command():
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 @handle_user_errors decorator must be placed immediately after @click.command() (or @group.command()) because decorators execute top-to-bottom during invocation, so placing it lower in the stack means it won't catch errors from decorators above it.
The decorator:
- Converts
ExpectedErrorexceptions toclick.ClickExceptionfor clean display - Transforms parameter names in messages to CLI equivalents:
- Backtick-wrapped names:
`option_a`→--option-a - Underscore names:
option_a→--option-a
- Backtick-wrapped names:
- Adds the
--tracebackflag to the command
Guidelines
- Use
ExpectedErrorsubclasses for errors the user can understand and potentially fix - Use standard exceptions (
RuntimeError,ValueError, etc.) for unexpected cases that need stack traces - Reference parameters with backticks (at least if it doesn't have an underscore) in error messages for automatic CLI name transformation
- Keep error messages concise and actionable