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

    bugDeprecated: use type/bug. Kept for issue history; will be removed in milestone 0.10.0.good first issueGood for newcomers

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions