Skip to content

policy: load_policy() raises OSError on macOS when policy YAML > 1023 bytes #848

@danielmeppiel

Description

@danielmeppiel

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

  1. 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.)
  2. Host it at <org>/.github/apm-policy.yml so install-time discovery picks it up.
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggood first issueGood for newcomers

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions