Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit d75df76

Browse files
mangelajocoderabbitai[bot]
authored andcommitted
j: better error if JUMPSTARTER_HOST isn't set (#739)
* j: better error if JUMPSTARTER_HOST isn't set until now if j is called outside of a jmp shell, a ugly exception is thrown, this patch produces an error explaining what's going on. * Raise exceptions not in group Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> (cherry picked from commit 4402736)
1 parent 4007374 commit d75df76

4 files changed

Lines changed: 45 additions & 8 deletions

File tree

packages/jumpstarter-cli-common/jumpstarter_cli_common/exceptions.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,26 @@ def wrapped(*args, **kwargs):
106106
return decorator
107107

108108

109+
def find_exception_in_group(
110+
eg: BaseExceptionGroup, exc_type: type[BaseException], *, fix_tracebacks: bool = False
111+
) -> BaseException | None:
112+
"""
113+
Find the first exception of a specific type in an ExceptionGroup.
114+
115+
Args:
116+
eg: The ExceptionGroup to search
117+
exc_type: The exception type to find
118+
fix_tracebacks: Whether to fix tracebacks in leaf exceptions
119+
120+
Returns:
121+
The first matching exception, or None if not found
122+
"""
123+
for exc in leaf_exceptions(eg, fix_tracebacks=fix_tracebacks):
124+
if isinstance(exc, exc_type):
125+
return exc
126+
return None
127+
128+
109129
# https://peps.python.org/pep-0654/
110130
def leaf_exceptions(self: BaseExceptionGroup, *, fix_tracebacks: bool = True) -> list[BaseException]:
111131
"""

packages/jumpstarter-cli/jumpstarter_cli/j.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,32 @@
66
import click
77
from anyio import create_task_group, get_cancelled_exc_class, run, to_thread
88
from anyio.from_thread import BlockingPortal
9-
from jumpstarter_cli_common.exceptions import async_handle_exceptions, leaf_exceptions
9+
from jumpstarter_cli_common.exceptions import (
10+
ClickExceptionRed,
11+
async_handle_exceptions,
12+
find_exception_in_group,
13+
leaf_exceptions,
14+
)
1015
from jumpstarter_cli_common.signal import signal_handler
1116
from rich import traceback
1217

18+
from jumpstarter.common.exceptions import EnvironmentVariableNotSetError
1319
from jumpstarter.utils.env import env_async
1420

1521

1622
async def j_async():
1723
@async_handle_exceptions
1824
async def cli():
19-
async with BlockingPortal() as portal:
20-
with ExitStack() as stack:
21-
async with env_async(portal, stack) as client:
22-
await to_thread.run_sync(lambda: client.cli()(standalone_mode=False))
23-
25+
try:
26+
async with BlockingPortal() as portal:
27+
with ExitStack() as stack:
28+
async with env_async(portal, stack) as client:
29+
await to_thread.run_sync(lambda: client.cli()(standalone_mode=False))
30+
except BaseExceptionGroup as eg:
31+
# Handle exceptions wrapped in ExceptionGroup (e.g., from task groups)
32+
if exc := find_exception_in_group(eg, EnvironmentVariableNotSetError):
33+
raise ClickExceptionRed(f"Error: the j command must be used inside a jmp shell: {exc}") from eg
34+
raise eg
2435
try:
2536
async with create_task_group() as tg:
2637
tg.start_soon(signal_handler, tg.cancel_scope)
@@ -29,7 +40,6 @@ async def cli():
2940
await cli()
3041
finally:
3142
tg.cancel_scope.cancel()
32-
3343
except* click.ClickException as excgroup:
3444
for exc in leaf_exceptions(excgroup):
3545
cast(click.ClickException, exc).show()

packages/jumpstarter/jumpstarter/common/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,9 @@ class ReauthenticationFailed(JumpstarterException):
7373
"""Raised when a re-authentication fails."""
7474

7575
pass
76+
77+
78+
class EnvironmentVariableNotSetError(JumpstarterException):
79+
"""Raised when a environment variable is not set."""
80+
81+
pass

packages/jumpstarter/jumpstarter/utils/env.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from anyio.from_thread import start_blocking_portal
55

66
from jumpstarter.client import client_from_path
7+
from jumpstarter.common.exceptions import EnvironmentVariableNotSetError
78
from jumpstarter.config.client import ClientConfigV1Alpha1Drivers
89
from jumpstarter.config.env import JUMPSTARTER_HOST
910

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

2425
drivers = ClientConfigV1Alpha1Drivers()
2526

0 commit comments

Comments
 (0)