From 3a66df26e25a1b650e19752ca0c40022fba2918c Mon Sep 17 00:00:00 2001 From: Julio Rodriguez Date: Tue, 7 Jul 2026 14:54:35 -0600 Subject: [PATCH] fix(local): warn when CodeUri path does not exist before mounting When running sam local invoke, start-api or start-lambda with a CodeUri (or layer ContentUri) that does not exist on the local machine, the path was silently mounted as an empty directory in the container. The invocation then failed with a secondary error (import or handler not found) that hides the real problem. Log a warning in LambdaRuntime._get_code_dir when the resolved code path does not exist, pointing the user at the CodeUri/ContentUri and suggesting sam build, so the root cause is visible before the container starts. Fixes #1754 --- samcli/local/lambdafn/runtime.py | 11 +++++-- tests/unit/local/lambdafn/test_runtime.py | 40 +++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/samcli/local/lambdafn/runtime.py b/samcli/local/lambdafn/runtime.py index 94efe75327..5070b33694 100644 --- a/samcli/local/lambdafn/runtime.py +++ b/samcli/local/lambdafn/runtime.py @@ -426,8 +426,7 @@ def _get_code_dir(self, code_path: str) -> str: This method handles a few different cases for ``code_path``: - ``code_path``is a existent zip/jar file: Unzip in a temp directory and return the temp directory - ``code_path`` is a existent directory: Return this immediately - - ``code_path`` is a file/dir that does not exist: Return it as is. May be this method is not clever to - detect the existence of the path + - ``code_path`` is a file/dir that does not exist: Log a warning and return it as is Parameters ---------- @@ -441,6 +440,14 @@ def _get_code_dir(self, code_path: str) -> str: Directory containing Lambda function code. It can be mounted directly in container """ + if code_path and not os.path.exists(code_path): + LOG.warning( + "Local code path '%s' does not exist. It will be mounted as an empty directory in the " + "container, which will likely cause the invocation to fail. Verify the CodeUri/ContentUri " + "of your function or layer, or run 'sam build' if the path is a build artifact.", + code_path, + ) + if code_path and os.path.isfile(code_path) and code_path.endswith(self.SUPPORTED_ARCHIVE_EXTENSIONS): decompressed_dir: str = _unzip_file(code_path, mount_symlinks=self._mount_symlinks) self._temp_uncompressed_paths_to_be_cleaned += [decompressed_dir] diff --git a/tests/unit/local/lambdafn/test_runtime.py b/tests/unit/local/lambdafn/test_runtime.py index b85b5a5f0f..9fb6e2a043 100644 --- a/tests/unit/local/lambdafn/test_runtime.py +++ b/tests/unit/local/lambdafn/test_runtime.py @@ -851,6 +851,46 @@ def test_must_return_a_valid_file(self, unzip_file_mock, shutil_mock, os_mock): # Because we never unzipped anything, we should never delete shutil_mock.rmtree.assert_not_called() + @patch("samcli.local.lambdafn.runtime.LOG") + @patch("samcli.local.lambdafn.runtime.os") + @patch("samcli.local.lambdafn.runtime.shutil") + @patch("samcli.local.lambdafn.runtime._unzip_file") + def test_must_warn_when_code_path_does_not_exist(self, unzip_file_mock, shutil_mock, os_mock, log_mock): + """ + Input is a path that does not exist on the local machine + """ + code_path = "/nonexistent/path" + + os_mock.path.exists.return_value = False + os_mock.path.isfile.return_value = False + + result = self.runtime._get_code_dir(code_path) + # code path must still be returned as is + self.assertEqual(result, code_path) + + unzip_file_mock.assert_not_called() + log_mock.warning.assert_called_once() + # The warning must include the offending path + self.assertIn(code_path, log_mock.warning.call_args[0]) + + @patch("samcli.local.lambdafn.runtime.LOG") + @patch("samcli.local.lambdafn.runtime.os") + @patch("samcli.local.lambdafn.runtime.shutil") + @patch("samcli.local.lambdafn.runtime._unzip_file") + def test_must_not_warn_when_code_path_exists(self, unzip_file_mock, shutil_mock, os_mock, log_mock): + """ + Input is a directory that exists, no warning must be logged + """ + code_path = "codedir" + + os_mock.path.exists.return_value = True + os_mock.path.isfile.return_value = False + + result = self.runtime._get_code_dir(code_path) + self.assertEqual(result, code_path) + + log_mock.warning.assert_not_called() + class TestLambdaRuntime_unarchived_layer(TestCase): def setUp(self):