Skip to content

fix: Failing sandbox test on macOS#2991

Open
NZTimKeegan wants to merge 1 commit into
snowflakedb:mainfrom
NZTimKeegan:fix/sandbox-venv-macos-symlinks
Open

fix: Failing sandbox test on macOS#2991
NZTimKeegan wants to merge 1 commit into
snowflakedb:mainfrom
NZTimKeegan:fix/sandbox-venv-macos-symlinks

Conversation

@NZTimKeegan
Copy link
Copy Markdown

@NZTimKeegan NZTimKeegan commented May 7, 2026

Pre-review checklist

  • I've confirmed that instructions included in README.md are still correct after my changes in the codebase.
    • n/a no documentable behaviour change introduced.
  • I've added or updated automated unit tests to verify correctness of my new code.
    • n/a resolves failing existing test, no new behaviour introduced
  • I've added or updated integration tests to verify correctness of my new code.
    • n/a resolves failing existing test, no new behaviour introduced
  • I've confirmed that my changes are working by executing CLI's commands manually on MacOS.
  • I've confirmed that my changes are working by executing CLI's commands manually on Windows.
    • I don't have access to a Windows environment
  • I've confirmed that my changes are up-to-date with the target branch.
  • I've described my changes in the release notes.
  • I've described my changes in the section below.
  • I've described my changes in the documentation.
    • n/a no documentable behaviour change introduced.

Changes description

On macOS, python-build-standalone (used by hatch/uv) embeds @executable_path/../lib/libpythonX.Y.dylib. When EnvBuilder copies the binary into a new venv, @executable_path resolves to the venv's bin/ directory where the dylib is absent, causing dyld to abort. Using symlinks instead lets macOS resolve the reference back to the original installation.

Failing Test

The failing test is test_sandbox_env_builder.

Below is the output when run on the main branch on macOS.

Command:

pytest tests/nativeapp/codegen/test_sandbox.py::test_sandbox_env_builder

Result:

============================= test session starts ==============================
platform darwin -- Python 3.10.19, pytest-8.4.1, pluggy-1.6.0 -- /Users/tim.keegan/Library/Application Support/hatch/env/virtual/snowflake-cli/MbNJCT--/snowflake-cli/bin/python
cachedir: .pytest_cache
Using --randomly-seed=995316962
rootdir: /Users/tim.keegan/Code/mine/snowflake-cli
configfile: pyproject.toml
plugins: randomly-3.16.0, syrupy-4.9.1, Faker-37.4.0, pytest_httpserver-1.1.3
collecting ... collected 1 item

tests/nativeapp/codegen/test_sandbox.py::test_sandbox_env_builder FAILED [100%]

=================================== FAILURES ===================================
___________________________ test_sandbox_env_builder ___________________________

temporary_directory = '/var/folders/df/f392s7657q1bvy47frmy_qqh0000gp/T/tmpzgmn4b1o'

    @skip_snowpark_on_newest_python
    def test_sandbox_env_builder(temporary_directory):
        env_path = Path(temporary_directory) / "venv"
        builder = sandbox.SandboxEnvBuilder(env_path)
        builder.ensure_created()  # exercise the creation path
    
>       builder.run_python("--version")  # should not raise an exception

/Users/tim.keegan/Code/mine/snowflake-cli/tests/nativeapp/codegen/test_sandbox.py:834: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/Users/tim.keegan/Code/mine/snowflake-cli/src/snowflake/cli/_plugins/nativeapp/codegen/sandbox.py:300: in run_python
    return subprocess.check_output(positional_args, **kwargs)
/Users/tim.keegan/Library/Application Support/hatch/env/virtual/.pythons/3.10/python/lib/python3.10/subprocess.py:421: in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

input = None, capture_output = False, timeout = None, check = True
popenargs = (['/var/folders/df/f392s7657q1bvy47frmy_qqh0000gp/T/tmpzgmn4b1o/venv/bin/python', '-E', '--version'],)
kwargs = {'cwd': '/var/folders/df/f392s7657q1bvy47frmy_qqh0000gp/T/tmpzgmn4b1o/venv', 'stderr': -2, 'stdout': -1}
process = <Popen: returncode: -6 args: ['/var/folders/df/f392s7657q1bvy47frmy_qqh0000g...>
stdout = b"dyld[82289]: Library not loaded: @executable_path/../lib/libpython3.10.dylib\n  Referenced from: <4C4C446E-5555-3144...: '/private/var/folders/df/f392s7657q1bvy47frmy_qqh0000gp/T/tmpzgmn4b1o/venv/lib/libpython3.10.dylib' (no such file)\n"
stderr = None, retcode = -6

    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout is given, and the process takes too long, a TimeoutExpired
        exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['/var/folders/df/f392s7657q1bvy47frmy_qqh0000gp/T/tmpzgmn4b1o/venv/bin/python', '-E', '--version']' died with <Signals.SIGABRT: 6>.

/Users/tim.keegan/Library/Application Support/hatch/env/virtual/.pythons/3.10/python/lib/python3.10/subprocess.py:526: CalledProcessError
=============================== warnings summary ===============================
../../../Library/Application Support/hatch/env/virtual/snowflake-cli/MbNJCT--/snowflake-cli/lib/python3.10/site-packages/_pytest/config/__init__.py:833
  /Users/tim.keegan/Library/Application Support/hatch/env/virtual/snowflake-cli/MbNJCT--/snowflake-cli/lib/python3.10/site-packages/_pytest/config/__init__.py:833: PytestAssertRewriteWarning: Module already imported so cannot be rewritten; tests_common
    self.import_plugin(import_spec)

../../../Library/Application Support/hatch/env/virtual/snowflake-cli/MbNJCT--/snowflake-cli/lib/python3.10/site-packages/_pytest/config/__init__.py:833
  /Users/tim.keegan/Library/Application Support/hatch/env/virtual/snowflake-cli/MbNJCT--/snowflake-cli/lib/python3.10/site-packages/_pytest/config/__init__.py:833: PytestAssertRewriteWarning: Module already imported so cannot be rewritten; tests.testing_utils
    self.import_plugin(import_spec)

../../../Library/Application Support/hatch/env/virtual/snowflake-cli/MbNJCT--/snowflake-cli/lib/python3.10/site-packages/typer/params.py:206: 40 warnings
  /Users/tim.keegan/Library/Application Support/hatch/env/virtual/snowflake-cli/MbNJCT--/snowflake-cli/lib/python3.10/site-packages/typer/params.py:206: DeprecationWarning: The 'is_flag' and 'flag_value' parameters are not supported by Typer and will be removed entirely in a future release.
    return OptionInfo(

src/snowflake/cli/api/project/schemas/v1/native_app/package.py:70
  /Users/tim.keegan/Code/mine/snowflake-cli/src/snowflake/cli/api/project/schemas/v1/native_app/package.py:70: PydanticDeprecatedSince212: Using `@model_validator` with mode='after' on a classmethod is deprecated. Instead, use an instance method. See the documentation at https://docs.pydantic.dev/2.12/concepts/validators/#model-after-validator. Deprecated in Pydantic V2.12 to be removed in V3.0.
    def validate_no_scripts_and_post_deploy(cls, value: Package):

src/snowflake/cli/_plugins/nativeapp/entities/models/event_sharing_telemetry.py:34
  /Users/tim.keegan/Code/mine/snowflake-cli/src/snowflake/cli/_plugins/nativeapp/entities/models/event_sharing_telemetry.py:34: PydanticDeprecatedSince212: Using `@model_validator` with mode='after' on a classmethod is deprecated. Instead, use an instance method. See the documentation at https://docs.pydantic.dev/2.12/concepts/validators/#model-after-validator. Deprecated in Pydantic V2.12 to be removed in V3.0.
    def validate_telemetry_event_sharing(

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/nativeapp/codegen/test_sandbox.py::test_sandbox_env_builder - subprocess.CalledProcessError: Command '['/var/folders/df/f392s7657q1bvy47frmy_qqh0000gp/T/tmpzgmn4b1o/venv/bin/python', '-E', '--version']' died with <Signals.SIGABRT: 6>.
======================== 1 failed, 44 warnings in 0.36s ========================

@NZTimKeegan
Copy link
Copy Markdown
Author

I will execute the commands manually on macOS before moving out of draft

@NZTimKeegan
Copy link
Copy Markdown
Author

Commands run locally on macOS:

  1. command: snow --version

    • output: image
  2. command: snow --help

    • output: image

@NZTimKeegan
Copy link
Copy Markdown
Author

I'm unsure which snow command will exercise the changes added by this PR. I'd be happy to run additional checks if the automated checks are insufficient.

@NZTimKeegan NZTimKeegan marked this pull request as ready for review May 8, 2026 00:58
@NZTimKeegan NZTimKeegan requested a review from a team as a code owner May 8, 2026 00:58
@NZTimKeegan
Copy link
Copy Markdown
Author

Edited description to add reproduction command and observed output

On macOS, python-build-standalone (used by hatch/uv) embeds
@executable_path/../lib/libpythonX.Y.dylib. When EnvBuilder copies the
binary into a new venv, @executable_path resolves to the venv's bin/
directory where the dylib is absent, causing dyld to abort. Using symlinks
instead lets macOS resolve the reference back to the original installation.
@NZTimKeegan NZTimKeegan force-pushed the fix/sandbox-venv-macos-symlinks branch from 67314a1 to 16479e8 Compare May 11, 2026 01:39
@NZTimKeegan
Copy link
Copy Markdown
Author

Rebased & pushed to resolve conflicts (RELEASE-NOTES.md)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant