From d754041dfbcfe8dbac61d69308d28051b49399fd Mon Sep 17 00:00:00 2001 From: 10^8byte <3149579303@qq.com> Date: Thu, 23 Apr 2026 09:53:07 +0800 Subject: [PATCH 1/2] fix(policy): handle OSError from is_file() on macOS PATH_MAX limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When load_policy() receives a YAML string that exceeds ~1023 bytes, the is_file() syscall fails on macOS (PATH_MAX ≈ 1024) with OSError [Errno 63] File name too long. Wrap the call in try/except to gracefully fall back to treating the input as a YAML string. Fixes #848 --- src/apm_cli/policy/parser.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/apm_cli/policy/parser.py b/src/apm_cli/policy/parser.py index 16a26bc02..bbf375743 100644 --- a/src/apm_cli/policy/parser.py +++ b/src/apm_cli/policy/parser.py @@ -240,10 +240,14 @@ def load_policy(source: Union[str, Path]) -> Tuple[ApmPolicy, List[str]]: """ path = Path(source) if not isinstance(source, Path) else source - if path.is_file(): + try: + is_file = path.is_file() + except OSError: + is_file = False + + if is_file: raw = path.read_text(encoding="utf-8") else: - # Treat source as a YAML string raw = str(source) try: From e1ed429ec7608b818ce54b466954a06d3a8c8e6b Mon Sep 17 00:00:00 2001 From: 10^8byte <3149579303@qq.com> Date: Thu, 23 Apr 2026 23:23:17 +0800 Subject: [PATCH 2/2] fix: address review comments --- src/apm_cli/policy/parser.py | 46 +++++++++++++++++++++++++------- tests/unit/policy/test_parser.py | 20 ++++++++++++++ 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/apm_cli/policy/parser.py b/src/apm_cli/policy/parser.py index bbf375743..c5db510ff 100644 --- a/src/apm_cli/policy/parser.py +++ b/src/apm_cli/policy/parser.py @@ -2,6 +2,7 @@ from __future__ import annotations +import errno from pathlib import Path from typing import Any, List, Optional, Tuple, Union @@ -233,22 +234,49 @@ def _build_policy(data: dict) -> ApmPolicy: ) +def _looks_like_yaml_content(source: str) -> bool: + """Return True when a string is more likely inline YAML than a file path. + + This avoids probing the filesystem for large YAML payloads, which can raise + platform-specific path errors such as ENAMETOOLONG on macOS. + """ + stripped = source.lstrip() + + if "\n" in source or "\r" in source: + return True + + if stripped.startswith(("{", "[", "---", "- ")): + return True + + first_line = stripped.splitlines()[0] if stripped else "" + return ": " in first_line or first_line.endswith(":") + + def load_policy(source: Union[str, Path]) -> Tuple[ApmPolicy, List[str]]: """Load and validate an apm-policy.yml from a file path or YAML string. Returns (policy, warnings). Raises PolicyValidationError on invalid input. """ - path = Path(source) if not isinstance(source, Path) else source - - try: - is_file = path.is_file() - except OSError: - is_file = False + raw: str - if is_file: - raw = path.read_text(encoding="utf-8") + if isinstance(source, Path): + raw = source.read_text(encoding="utf-8") if source.is_file() else str(source) + elif _looks_like_yaml_content(source): + raw = source else: - raw = str(source) + path = Path(source) + try: + is_file = path.is_file() + except OSError as exc: + if exc.errno == errno.ENAMETOOLONG: + is_file = False + else: + raise + + if is_file: + raw = path.read_text(encoding="utf-8") + else: + raw = source try: data = yaml.safe_load(raw) diff --git a/tests/unit/policy/test_parser.py b/tests/unit/policy/test_parser.py index cf834bfb3..5fa0494b3 100644 --- a/tests/unit/policy/test_parser.py +++ b/tests/unit/policy/test_parser.py @@ -280,6 +280,26 @@ def test_version_coerced_to_string(self): policy, warnings = load_policy("version: '2.0'") self.assertEqual(policy.version, "2.0") + def test_long_yaml_string_does_not_crash(self): + """Long YAML strings (> PATH_MAX on macOS) must not raise OSError.""" + # Build a YAML payload larger than typical PATH_MAX limits (1024 bytes) + # so that Path.is_file() can raise ENAMETOOLONG on macOS. + long_comment = "# " + "x" * 2048 + "\n" + yaml_str = ( + long_comment + + "name: long-policy\n" + + "version: '1.0'\n" + + "enforcement: off\n" + ) + # Ensure the string is long enough to trigger ENAMETOOLONG on macOS + self.assertGreater(len(yaml_str), 1024) + + # This should parse as inline YAML, not as a file path + policy, warnings = load_policy(yaml_str) + self.assertEqual(policy.name, "long-policy") + self.assertEqual(policy.version, "1.0") + self.assertEqual(policy.enforcement, "off") + class TestLoadPolicyFromFile(unittest.TestCase): """Test load_policy from a file path."""