Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,26 @@ def wrapped(*args, **kwargs):
return decorator


def find_exception_in_group(
eg: BaseExceptionGroup, exc_type: type[BaseException], *, fix_tracebacks: bool = False
) -> BaseException | None:
"""
Find the first exception of a specific type in an ExceptionGroup.

Args:
eg: The ExceptionGroup to search
exc_type: The exception type to find
fix_tracebacks: Whether to fix tracebacks in leaf exceptions

Returns:
The first matching exception, or None if not found
"""
for exc in leaf_exceptions(eg, fix_tracebacks=fix_tracebacks):
if isinstance(exc, exc_type):
return exc
return None


# https://peps.python.org/pep-0654/
def leaf_exceptions(self: BaseExceptionGroup, *, fix_tracebacks: bool = True) -> list[BaseException]:
"""
Expand Down
24 changes: 17 additions & 7 deletions packages/jumpstarter-cli/jumpstarter_cli/j.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,32 @@
import click
from anyio import create_task_group, get_cancelled_exc_class, run, to_thread
from anyio.from_thread import BlockingPortal
from jumpstarter_cli_common.exceptions import async_handle_exceptions, leaf_exceptions
from jumpstarter_cli_common.exceptions import (
ClickExceptionRed,
async_handle_exceptions,
find_exception_in_group,
leaf_exceptions,
)
from jumpstarter_cli_common.signal import signal_handler
from rich import traceback

from jumpstarter.common.exceptions import EnvironmentVariableNotSetError
from jumpstarter.utils.env import env_async


async def j_async():
@async_handle_exceptions
async def cli():
async with BlockingPortal() as portal:
with ExitStack() as stack:
async with env_async(portal, stack) as client:
await to_thread.run_sync(lambda: client.cli()(standalone_mode=False))

try:
async with BlockingPortal() as portal:
with ExitStack() as stack:
async with env_async(portal, stack) as client:
await to_thread.run_sync(lambda: client.cli()(standalone_mode=False))
except BaseExceptionGroup as eg:
# Handle exceptions wrapped in ExceptionGroup (e.g., from task groups)
if exc := find_exception_in_group(eg, EnvironmentVariableNotSetError):
raise ClickExceptionRed(f"Error: the j command must be used inside a jmp shell: {exc}") from eg
raise eg
try:
async with create_task_group() as tg:
tg.start_soon(signal_handler, tg.cancel_scope)
Expand All @@ -29,7 +40,6 @@ async def cli():
await cli()
finally:
tg.cancel_scope.cancel()

except* click.ClickException as excgroup:
for exc in leaf_exceptions(excgroup):
cast(click.ClickException, exc).show()
Expand Down
6 changes: 6 additions & 0 deletions packages/jumpstarter/jumpstarter/common/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,9 @@ class ReauthenticationFailed(JumpstarterException):
"""Raised when a re-authentication fails."""

pass


class EnvironmentVariableNotSetError(JumpstarterException):
"""Raised when a environment variable is not set."""

pass
3 changes: 2 additions & 1 deletion packages/jumpstarter/jumpstarter/utils/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from anyio.from_thread import start_blocking_portal

from jumpstarter.client import client_from_path
from jumpstarter.common.exceptions import EnvironmentVariableNotSetError
from jumpstarter.config.client import ClientConfigV1Alpha1Drivers
from jumpstarter.config.env import JUMPSTARTER_HOST

Expand All @@ -19,7 +20,7 @@ async def env_async(portal, stack):
"""
host = os.environ.get(JUMPSTARTER_HOST, None)
if host is None:
raise RuntimeError(f"{JUMPSTARTER_HOST} not set")
raise EnvironmentVariableNotSetError(f"{JUMPSTARTER_HOST} not set")

drivers = ClientConfigV1Alpha1Drivers()

Expand Down
Loading