# App Calling & Return Values

In this section, we'll take a closer look at the `App.__call__()` method.

## Input Command

Typically, a Cyclopts app looks something like:

    from cyclopts import App

    app = App()

    @app.command
    def foo(a: int, b: int, c: int):
        print(a + b + c)

    app()

    $ my-script 1 2 3
    6

`App.__call__()` takes in an optional input that it parses into an action. If not specified, Cyclopts defaults to `sys.argv[1:]`, i.e. the list of command line arguments. An explicit string or list of strings can instead be passed in.

    app("foo 1 2 3")
    # 6
    app(["foo", "1", "2", "3"])
    # 6

If a string is passed in, it will be internally converted into a list using shlex.split.

## Return Value

The `app` invocation processes the command's return value based on `App.result_action`. By default, Cyclopts calls `sys.exit()` with an appropriate exit code:

    from cyclopts import App

    app = App()  # Default result_action="print_non_int_sys_exit"

    @app.command
    def success():
        return 0  # Exit code for success

    @app.command
    def greet(name: str) -> str:
        return f"Hello {name}!"  # Prints and exits with 0

    if __name__ == "__main__":
        app()  # Will call sys.exit with the returned 0 error code (success).

Installed scripts call `sys.exit()` with the returned value of the entry point. So the default Cyclopts `App.result_action` will have consistent behavior for standalone scripts and installed apps.

For embedding Cyclopts in other Python code or testing, use `result_action="return_value"` to get the raw command return value without calling `sys.exit()`:

    from cyclopts import App

    app = App(result_action="return_value")

    @app.command
    def foo(a: int, b: int, c: int):
        return a + b + c

    return_value = app("foo 1 2 3")  # no longer exits!
    print(f"The return value was: {return_value}.")
    # The return value was: 6.

See Result Action for all available modes and detailed behavior.

## Exception Handling and Exiting

For the most part, Cyclopts is hands-off when it comes to handling exceptions and exiting the application. However, by default, if there is a Cyclopts runtime error, like `CoercionError` or a `ValidationError`, then Cyclopts will perform a `sys.exit(1)`. This is to avoid displaying the unformatted, uncaught exception to the CLI user.

These behaviors can be controlled via `App` attributes or method parameters:

- `App.exit_on_error` - Calls `sys.exit(1)` on errors (defaults to `True`)

- `App.print_error` - Formatted errors are printed (defaults to `True`)

- `App.help_on_error` - The help-page is printed before errors (defaults to `False`)

- `App.verbose` - Include verbose error information that might be useful for developers using Cyclopts (defaults to `False`)

- `App.error_formatter` - Customize how error messages are formatted (defaults to `CycloptsPanel()`)

These attributes are inherited by child apps and can be overridden by providing parameters to method calls.

Note

Cyclopts separates normal output from error messages using two different consoles:

- `App.console` - Used for normal output like help messages and version information (defaults to stdout)

- `App.error_console` - Used for error messages like parsing errors and exceptions (defaults to stderr)

Setting at App Level:

    # Configure error handling at the app level
    app = App(
        exit_on_error=False,  # Don't exit on errors
        print_error=False,    # Don't print formatted errors
    )

    # Child apps inherit these settings
    child_app = App(name="child")
    app.command(child_app)

Method-Level Override:

    app("this-is-not-a-registered-command")
    print("this will not be printed since cyclopts exited above.")
    # ╭─ Error ─────────────────────────────────────────────────────────────╮
    # │ Unknown command "this-is-not-a-registered-command".                 │
    # ╰─────────────────────────────────────────────────────────────────────╯

    app("this-is-not-a-registered-command", exit_on_error=False, print_error=False)
    # Traceback (most recent call last):
    #   File "/cyclopts/scratch.py", line 9, in <module>
    #     app("this-is-not-a-registered-command", exit_on_error=False, print_error=False)
    #   File "/cyclopts/cyclopts/core.py", line 1102, in __call__
    #     command, bound, _ = self.parse_args(
    #   File "/cyclopts/cyclopts/core.py", line 1037, in parse_args
    #     command, bound, unused_tokens, ignored, argument_collection = self._parse_known_args(
    #   File "/cyclopts/cyclopts/core.py", line 966, in _parse_known_args
    #     raise UnknownCommandError(unused_tokens=unused_tokens)
    # cyclopts.exceptions.UnknownCommandError: Unknown command "this-is-not-a-registered-command".

    try:
        app("this-is-not-a-registered-command", exit_on_error=False, print_error=False)
    except CycloptsError:
        pass
    print("Execution continues since we caught the exception.")

With `exit_on_error=False`, the `UnknownCommandError` is raised the same as a normal python exception.

## Custom Error Formatting

By default, Cyclopts displays errors using `CycloptsPanel()`, which renders a Rich panel:

    ╭─ Error ───────────────────────────────────────────────╮
    │ Invalid value for VALUE: unable to convert "foo"      │
    │ into int.                                             │
    ╰───────────────────────────────────────────────────────╯

To customize this, set `App.error_formatter` to a callable that receives a `CycloptsError` and returns any Rich-printable object.

    from cyclopts import App, CycloptsError

    def my_error_formatter(e: CycloptsError):
        return f"[bold red]error[/bold red]: {e}"

    app = App(error_formatter=my_error_formatter)

    @app.default
    def main(value: int):
        pass

    $ my-app foo
    error: Invalid value for VALUE: unable to convert "foo" into int.

The formatter receives the full `CycloptsError` exception, which contains context like the `command_chain`, `argument`, and `target`. Use `str(e)` for just the message text.

Like other error-handling attributes, `error_formatter` can also be passed as a runtime override:

    app.parse_args("foo", error_formatter=my_error_formatter)