Summary
load_policy() raises an unhandled OSError [Errno 63] File name too long on macOS when the policy YAML content exceeds ~1023 bytes (PATH_MAX). The error escapes the install pipeline and surfaces as a confusing Failed to resolve APM dependencies with the entire policy YAML dumped as the "filename".
This blocks any project whose discovered org policy grows past the macOS PATH_MAX once comments/structure are added — i.e. any realistic enterprise policy.
Repro
- Author an
apm-policy.yml with normal structure + comments such that total bytes > 1023. (Easy to hit; a policy with all the standard sections and a few inline comments lands around 1.0–1.5 KB.)
- Host it at
<org>/.github/apm-policy.yml so install-time discovery picks it up.
- From a repo in that org, run
apm install against any dependency.
Observed:
[x] Failed to install APM dependencies: Failed to resolve APM dependencies: [Errno 63] File name too long: 'name:
devexpgbb-org-policy\nversion: "1.1.0"\n# Demo: enforcement flipped to block to show install-time governance teeth.\n#
... (entire policy YAML dumped) ...
Expected: policy parses successfully (or, if invalid, a clean PolicyValidationError).
Root cause
src/apm_cli/policy/parser.py:243 calls path.is_file() to decide whether source is a path or raw YAML:
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(): # <-- raises OSError when source is YAML content > PATH_MAX
raw = path.read_text(encoding="utf-8")
else:
raw = str(source)
On macOS, Path(long_yaml).stat() returns OSError [Errno 63] ENAMETOOLONG once the string exceeds PATH_MAX (~1024 bytes). On Linux the limit is higher (4096) so the bug is masked there, which is likely why CI didn't catch it.
The intent of the dual-input API is "accept both path and content"; the type-discrimination should not depend on a syscall that can fail.
Suggested fix
Wrap the existence check in try/except OSError, or stop using filesystem probing for type discrimination. Two options:
# Option A: defensive
try:
is_file = path.is_file()
except OSError:
is_file = False
if is_file:
raw = path.read_text(encoding="utf-8")
else:
raw = str(source)
# Option B: explicit content sniffing (cleaner)
if isinstance(source, Path) or ("\n" not in str(source) and Path(source).is_file()):
raw = Path(source).read_text(encoding="utf-8")
else:
raw = str(source)
Option A is a one-line surgical fix; Option B avoids the syscall entirely when the input clearly isn't a path.
Impact
- All call sites that pass raw policy content into
load_policy() are affected. Per grep:
src/apm_cli/policy/discovery.py:605 (project-local discovery)
src/apm_cli/policy/discovery.py:795 (URL fetch)
src/apm_cli/policy/discovery.py:877 (org .github fetch)
- Hits macOS only. Linux PATH_MAX = 4096 hides it for typical policies.
- Surfaces as an opaque error message that doesn't mention "policy" anywhere, making diagnosis hard.
Test cases to add
tests/unit/policy/test_parser.py: load_policy(yaml_content) with content of length 800, 1024, 1500, 4500 bytes — all must parse successfully without OSError, on both Linux and macOS.
Related
Summary
load_policy()raises an unhandledOSError [Errno 63] File name too longon macOS when the policy YAML content exceeds ~1023 bytes (PATH_MAX). The error escapes the install pipeline and surfaces as a confusingFailed to resolve APM dependencieswith the entire policy YAML dumped as the "filename".This blocks any project whose discovered org policy grows past the macOS PATH_MAX once comments/structure are added — i.e. any realistic enterprise policy.
Repro
apm-policy.ymlwith normal structure + comments such that total bytes > 1023. (Easy to hit; a policy with all the standard sections and a few inline comments lands around 1.0–1.5 KB.)<org>/.github/apm-policy.ymlso install-time discovery picks it up.apm installagainst any dependency.Observed:
Expected: policy parses successfully (or, if invalid, a clean
PolicyValidationError).Root cause
src/apm_cli/policy/parser.py:243callspath.is_file()to decide whethersourceis a path or raw YAML:On macOS,
Path(long_yaml).stat()returnsOSError [Errno 63] ENAMETOOLONGonce the string exceeds PATH_MAX (~1024 bytes). On Linux the limit is higher (4096) so the bug is masked there, which is likely why CI didn't catch it.The intent of the dual-input API is "accept both path and content"; the type-discrimination should not depend on a syscall that can fail.
Suggested fix
Wrap the existence check in
try/except OSError, or stop using filesystem probing for type discrimination. Two options:Option A is a one-line surgical fix; Option B avoids the syscall entirely when the input clearly isn't a path.
Impact
load_policy()are affected. Pergrep:src/apm_cli/policy/discovery.py:605(project-local discovery)src/apm_cli/policy/discovery.py:795(URL fetch)src/apm_cli/policy/discovery.py:877(org.githubfetch)Test cases to add
tests/unit/policy/test_parser.py:load_policy(yaml_content)with content of length 800, 1024, 1500, 4500 bytes — all must parse successfully without OSError, on both Linux and macOS.Related
DevExpGbb/.github/apm-policy.ymlper the W4 matrix in feat(policy): enforce apm-policy.yml at install time #832 (feat(policy): enforce apm-policy.yml at install time #832 (comment)). The matrix run used a policy small enough to dodge this; longer real-world policies hit it immediately.