diff --git a/.changeset/tolerate-non-numeric-step.md b/.changeset/tolerate-non-numeric-step.md new file mode 100644 index 0000000000..80ee3ccb96 --- /dev/null +++ b/.changeset/tolerate-non-numeric-step.md @@ -0,0 +1,5 @@ +--- +"@e2b/python-sdk": patch +--- + +fix(python-sdk): don't crash the build error path on a non-numeric reason.step diff --git a/packages/python-sdk/e2b/template/utils.py b/packages/python-sdk/e2b/template/utils.py index b61101b307..45979cda1d 100644 --- a/packages/python-sdk/e2b/template/utils.py +++ b/packages/python-sdk/e2b/template/utils.py @@ -406,7 +406,7 @@ def pad_octal(mode: int) -> str: return f"{mode:04o}" -def get_build_step_index(step: str, stack_traces_length: int) -> int: +def get_build_step_index(step: str, stack_traces_length: int) -> Optional[int]: """ Get the array index for a build step based on its name. @@ -414,11 +414,12 @@ def get_build_step_index(step: str, stack_traces_length: int) -> int: - BASE_STEP_NAME: Returns 0 (first step) - FINALIZE_STEP_NAME: Returns the last index - Numeric strings: Converted to number + - Anything else: None (no matching local stack trace) :param step: Build step name or number as string :param stack_traces_length: Total number of stack traces (used for FINALIZE_STEP_NAME) - :return: Index for the build step + :return: Index for the build step, or None if the step has no usable index """ if step == BASE_STEP_NAME: return 0 @@ -426,7 +427,10 @@ def get_build_step_index(step: str, stack_traces_length: int) -> int: if step == FINALIZE_STEP_NAME: return stack_traces_length - 1 - return int(step) + try: + return int(step) + except ValueError: + return None def read_gcp_service_account_json( diff --git a/packages/python-sdk/e2b/template_async/build_api.py b/packages/python-sdk/e2b/template_async/build_api.py index 9a822cd516..3b9445f482 100644 --- a/packages/python-sdk/e2b/template_async/build_api.py +++ b/packages/python-sdk/e2b/template_async/build_api.py @@ -299,7 +299,7 @@ async def poll_status() -> TemplateBuildStatusResponse: step_index = get_build_step_index( build_status.reason.step, len(stack_traces) ) - if step_index < len(stack_traces): + if step_index is not None and 0 <= step_index < len(stack_traces): traceback = stack_traces[step_index] raise BuildException( diff --git a/packages/python-sdk/e2b/template_sync/build_api.py b/packages/python-sdk/e2b/template_sync/build_api.py index 2bc918ab2f..4d5d7bd656 100644 --- a/packages/python-sdk/e2b/template_sync/build_api.py +++ b/packages/python-sdk/e2b/template_sync/build_api.py @@ -289,7 +289,7 @@ def poll_status() -> TemplateBuildStatusResponse: step_index = get_build_step_index( build_status.reason.step, len(stack_traces) ) - if step_index < len(stack_traces): + if step_index is not None and 0 <= step_index < len(stack_traces): traceback = stack_traces[step_index] raise BuildException( diff --git a/packages/python-sdk/tests/test_build_step_index.py b/packages/python-sdk/tests/test_build_step_index.py new file mode 100644 index 0000000000..926084947a --- /dev/null +++ b/packages/python-sdk/tests/test_build_step_index.py @@ -0,0 +1,21 @@ +from e2b.template.consts import BASE_STEP_NAME, FINALIZE_STEP_NAME +from e2b.template.utils import get_build_step_index + + +def test_base_maps_to_first(): + assert get_build_step_index(BASE_STEP_NAME, 5) == 0 + + +def test_finalize_maps_to_last(): + assert get_build_step_index(FINALIZE_STEP_NAME, 5) == 4 + + +def test_numeric_string_maps_to_index(): + assert get_build_step_index("3", 5) == 3 + + +def test_non_numeric_step_returns_none(): + # e2b Cloud itself reports "optimize" / "resize-disk" for those phases. + # None means "no matching local stack trace", not an error. + assert get_build_step_index("optimize", 5) is None + assert get_build_step_index("RUN apt-get update", 5) is None