Skip to content

[BUG] Admin-tier lifecycle policy dir hardcodes C:\ProgramData, so it silently loads nothing when Windows is not on C: #2684

Description

Describe the bug

_get_policy_scripts_dir() hardcodes the C: drive for the admin policy tier:

# src/apm_cli/core/lifecycle_scripts.py:346-351
def _get_policy_scripts_dir() -> Path:
    """Return the platform-specific policy scripts directory."""
    system = platform.system()
    if system == "Windows":
        return Path(r"C:\ProgramData\APM\policy.d")
    return Path("/etc/apm/policy.d")

Windows does not define ProgramData as a constant. It is stored in the registry as an
unexpanded string and expanded at runtime:

> (Get-Item 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList').GetValue(
      'ProgramData', $null, 'DoNotExpandEnvironmentNames')
%SystemDrive%\ProgramData

So on a machine whose %SystemDrive% is not C:, apm reads the admin tier from a
directory that is not that machine's ProgramData. The failure is silent:
_load_scripts_from_dir() returns [] for a missing directory (line 373), with no
warning. Admin-tier lifecycle policy contributes zero scripts and the tier documented as
the trust anchor quietly does nothing.

The user tier in the same module already resolves from the environment
(_get_user_apm_yml() honours APM_HOME), so the two tiers disagree about whether
location is configurable.

Expected behavior

Resolve the directory from the environment rather than a literal, e.g.
Path(os.environ.get("ProgramData", r"C:\ProgramData")) / "APM" / "policy.d".

To Reproduce

I could not reproduce end-user impact here: this machine's %SystemDrive% is C:, so the
hardcoded path happens to be correct. What I did verify by running it is that the function
ignores the environment entirely, while the user tier beside it does not:

import os
from apm_cli.core import lifecycle_scripts as ls

print(ls._get_policy_scripts_dir())   # C:\ProgramData\APM\policy.d
os.environ["ProgramData"] = r"D:\ProgramData"
print(ls._get_policy_scripts_dir())   # C:\ProgramData\APM\policy.d   <- unchanged
os.environ["APM_HOME"] = r"D:\apm-home"
print(ls._get_user_apm_yml())         # D:\apm-home\apm.yml           <- follows env

Environment

  • OS: Windows 11 Home
  • Python Version: 3.13.13
  • APM Version: 0.28.0 (lifecycle_scripts.py byte-identical to main @ 88cf78a)

Additional context

No test references ProgramData, so the literal does not read as a deliberately pinned
choice. One judgement call belongs to you rather than to me: on a non-C: system that
already has scripts in C:\ProgramData\APM\policy.d, switching to the environment would
stop reading them. That is why this is an issue and not a patch.

Written with AI assistance. On this machine I ran the probe above and the registry query,
and read _get_policy_scripts_dir and _load_scripts_from_dir in full; I have not tested
on an install whose SystemDrive is not C:, and have not claimed otherwise above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions