Skip to content

fix: Systematic Windows path compatibility hardening #412

@danielmeppiel

Description

@danielmeppiel

Problem

Windows CI tests fail repeatedly due to two Path.relative_to() pitfalls:

  1. str(Path.relative_to()) produces backslashes on Windows — breaks string comparisons, stored paths, and test assertions
  2. Path.relative_to() raises ValueError when Windows 8.3 short names (RUNNER~1) don't match .resolve()d long names (runneradmin)

We've been fixing these ad-hoc (PRs #410, #411) but have ~20+ call sites across the codebase with inconsistent handling. Need a systemic fix.

Proposed solution

1. Add a portable_relpath() utility function

Create a single helper in src/apm_cli/utils/paths.py that makes the correct pattern the easy pattern:

def portable_relpath(path: Path, base: Path) -> str:
    """Return a forward-slash relative path, resolving both sides first.
    
    Handles Windows 8.3 short names and ensures consistent POSIX output.
    Falls back to the absolute path string if path is not under base.
    """
    try:
        return path.resolve().relative_to(base.resolve()).as_posix()
    except ValueError:
        return str(path)

This centralizes the three-step pattern (.resolve() both sides → .relative_to().as_posix()) into one call that's impossible to get wrong.

2. Migrate all call sites (~23 locations)

Phase 1 (P0): str(path.relative_to()) without .as_posix() — ~9 locations

  • src/apm_cli/compilation/context_optimizer.py:352
  • src/apm_cli/compilation/agents_compiler.py:634,650
  • src/apm_cli/security/gate.py:105
  • src/apm_cli/integration/prompt_integrator.py
  • src/apm_cli/integration/instruction_integrator.py:81
  • src/apm_cli/integration/agent_integrator.py:164,179,188
  • src/apm_cli/integration/hook_integrator.py
  • src/apm_cli/integration/command_integrator.py

Phase 2 (P1): relative_to() without .resolve() on both sides — ~12 locations

  • src/apm_cli/compilation/context_optimizer.py:424,515,658,799,815,1115
  • src/apm_cli/compilation/distributed_compiler.py:545,654,684,691
  • src/apm_cli/compilation/claude_formatter.py:302
  • src/apm_cli/compilation/template_builder.py:48

Phase 3 (P2): Logging cosmetics — 2 locations

  • src/apm_cli/commands/uninstall/engine.py:135,216

3. Add a CI lint guard

Add a grep-based check to CI that catches raw relative_to()str() patterns:

# Fail if any new code uses str(x.relative_to(y)) instead of portable_relpath()
! grep -rn 'str(.*\.relative_to(' src/apm_cli/ --include="*.py" | grep -v portable_relpath

This prevents backsliding without adding heavy tooling.

Notes

  • Integrator relative_to() calls need verification — some may be display-only (safe), others may feed into apm.lock.yaml keys (the lockfile write path already uses .as_posix())
  • os.chdir() + TemporaryDirectory test issues were fully fixed in PR fix: Windows test failures in config command and agents compiler #410
  • All changes are mechanical and low-risk
  • Run Windows CI to confirm zero failures after the sweep

References

Metadata

Metadata

Labels

acceptedDirection approved, safe to start workenhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions