Summary
apm install can materialize executable skill scripts as plain non-executable files on some install paths.
This matters for packages that ship runnable skill scripts, e.g. a skill containing scripts/do-driver tracked in git as 100755. The generated skill file is present after install, but invoking it directly fails because the executable bit was not preserved.
Expected behavior
Executable mode metadata is preserved when APM materializes package files.
For a source file tracked or archived as executable:
test -x <generated-skill-path>/scripts/do-driver
should pass after:
Observed behavior
In a downstream consumer install, the workflow driver file was present but not executable.
The affected package source has the file tracked executable in git:
100755 .apm/skills/do/scripts/do-driver
Likely affected paths
I found two places in apm-cli 0.16.0 that appear to copy file contents without preserving mode metadata.
1. Artifactory ZIP extraction
apm_cli/deps/download_strategies.py manually streams ZIP entries to disk:
with zf.open(member) as src, open(dest, "wb") as dst:
dst.write(src.read())
That writes bytes but does not apply the ZIP entry's Unix mode from ZipInfo.external_attr.
There is already a precedent in apm_cli/deps/registry/extractor.py, where registry ZIP extraction reads the high 16 bits of external_attr and applies mode bits:
unix_mode = (info.external_attr >> 16) & 0xFFFF
...
if unix_mode:
os.chmod(target, unix_mode & 0o755)
The Artifactory archive extractor should probably mirror that behavior.
2. Reflink copy fast path
apm_cli/utils/file_ops.py has a reflink-backed copy helper:
if clone_file(src, dst):
return dst
The fallback path uses shutil.copy2(...), which preserves metadata, but the successful reflink fast path returns before copystat/mode preservation.
Suggested fix:
if clone_file(src, dst):
shutil.copystat(src, dst, follow_symlinks=follow_symlinks)
return dst
Why this is not just a package issue
The source package has the executable bit. Normal git checkout/copy paths preserve it. The failure appears when APM materializes package files through an archive/proxy or reflink path that writes/clones bytes without copying stat metadata.
Suggested regression tests
Add tests that materialize a package or copied tree containing a 100755 script and assert the installed/generated file remains executable.
Useful cases:
-
Artifactory/ZIP extraction:
- Create a ZIP entry with Unix mode
0o100755 in external_attr.
- Extract through the Artifactory archive path.
- Assert the extracted file is executable.
-
Reflink copy helper:
- Mock or exercise a successful
clone_file(src, dst) path.
- Source file mode is executable.
- Assert destination mode preserves executable bits.
-
End-to-end install canary if feasible:
- Package with a skill script tracked executable.
apm install -t claude or another target.
- Assert generated
skills/<name>/scripts/<script> is executable.
Environment
apm-cli version: 0.16.0
- Latest PyPI release checked:
0.16.0
- Latest GitHub release checked:
v0.16.0
- Upstream
main was ahead of v0.16.0 at time of investigation, but I did not find an existing issue for executable-bit preservation.
Summary
apm installcan materialize executable skill scripts as plain non-executable files on some install paths.This matters for packages that ship runnable skill scripts, e.g. a skill containing
scripts/do-drivertracked in git as100755. The generated skill file is present after install, but invoking it directly fails because the executable bit was not preserved.Expected behavior
Executable mode metadata is preserved when APM materializes package files.
For a source file tracked or archived as executable:
should pass after:
Observed behavior
In a downstream consumer install, the workflow driver file was present but not executable.
The affected package source has the file tracked executable in git:
Likely affected paths
I found two places in
apm-cli0.16.0that appear to copy file contents without preserving mode metadata.1. Artifactory ZIP extraction
apm_cli/deps/download_strategies.pymanually streams ZIP entries to disk:That writes bytes but does not apply the ZIP entry's Unix mode from
ZipInfo.external_attr.There is already a precedent in
apm_cli/deps/registry/extractor.py, where registry ZIP extraction reads the high 16 bits ofexternal_attrand applies mode bits:The Artifactory archive extractor should probably mirror that behavior.
2. Reflink copy fast path
apm_cli/utils/file_ops.pyhas a reflink-backed copy helper:The fallback path uses
shutil.copy2(...), which preserves metadata, but the successful reflink fast path returns beforecopystat/mode preservation.Suggested fix:
Why this is not just a package issue
The source package has the executable bit. Normal git checkout/copy paths preserve it. The failure appears when APM materializes package files through an archive/proxy or reflink path that writes/clones bytes without copying stat metadata.
Suggested regression tests
Add tests that materialize a package or copied tree containing a
100755script and assert the installed/generated file remains executable.Useful cases:
Artifactory/ZIP extraction:
0o100755inexternal_attr.Reflink copy helper:
clone_file(src, dst)path.End-to-end install canary if feasible:
apm install -t claudeor another target.skills/<name>/scripts/<script>is executable.Environment
apm-cliversion:0.16.00.16.0v0.16.0mainwas ahead ofv0.16.0at time of investigation, but I did not find an existing issue for executable-bit preservation.