You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
apm unpack writes apm.lock.yaml and apm.yml into the output directory, even though the CLI's own documentation states these are bundle metadata, not output. This contract violation cascades up the stack:
error: Your local changes to the following files would be overwritten by checkout:
apm.lock.yaml
ERR_API: Failed to checkout PR branch: The process '/usr/bin/git' failed with exit code 1
Originating scenario (plain English)
An agentic workflow built on the documented gh-aw template shared/apm.md runs in two phases on a PR:
Trusted base phase: checks out main, downloads the APM bundle (built earlier with isolated: 'true' into /tmp/gh-aw/apm-workspace), then calls microsoft/apm-action in restore mode to deploy primitives into the workspace. As a side-effect, the action writes apm.lock.yaml and apm.yml back into the workspace.
PR checkout phase: gh-aw runs git checkout -B <branch> origin/pr-head. Git refuses because the workspace is dirty.
When the PR also modifies apm.lock.yaml (which is exactly what every dependency-related PR does), the writes collide and the checkout aborts. The agent never starts.
Root cause: apm unpack contract violation
apm unpack's documented contract states that apm.lock.yaml is bundle metadata and is not copied to the output directory. The implementation does the opposite: it writes the full bundle contents — including apm.lock.yaml and apm.yml — into the output directory. Anything depending on apm unpack (most notably microsoft/apm-action) inherits the bug.
Proposed fix: align CLI with its own contract
Goal:apm unpack <bundle> --output <dir> writes only what the documented contract says it should: agent primitives under .github/{skills,agents,instructions,prompts}/ and apm_modules/. apm.lock.yaml and apm.yml are not written to <dir>.
Written (no-clobber: existing files in <dir> win, mirroring discover_primitives_with_dependencies priority)
<dir>/apm_modules/
Written
Written (wholesale; gitignored / package-owned)
<dir>/apm.lock.yaml
Written
Not written
<dir>/apm.yml
Written
Not written
Bundle metadata access
Implicit (via written files in <dir>)
Explicit: --metadata-dir <path> flag (optional) writes apm.lock.yaml + apm.yml to a separate caller-controlled path
Why no-clobber for primitives
Today, unpacker.py:37-39 documents: "If a local file has the same name as a bundle file, the bundle file wins (overwrite)." This contradicts APM's own discovery priority (discover_primitives_with_dependencies: local primitives have highest priority). Aligning unpack's collision behavior with discovery's collision behavior makes the model consistent end-to-end.
Why no opt-in flag
Callers who relied on apm.lock.yaml being written to the output dir were relying on undocumented behavior that contradicts the CLI's own contract. Treat as a Fixed bullet in CHANGELOG, not a breaking-flag dance.
microsoft/apm own copy of shared/apm.md: keep the temporary post-restore git checkout -- . 2>/dev/null || true workaround until microsoft/apm-action@v2 is available; then drop it and re-import gh-aw's shared/apm.md upstream.
Direct CLI users invoking apm unpack: those who genuinely need the lockfile post-unpack add --metadata-dir <path>.
Acceptance criteria
apm unpack <bundle> --output <dir> does not write apm.lock.yaml or apm.yml into <dir>.
apm unpack <bundle> --output <dir> with a pre-existing tracked primitive (e.g. <dir>/.github/agents/foo.agent.md) does not overwrite it; the local file wins.
apm unpack <bundle> --output <dir> --metadata-dir <metadir> writes apm.lock.yaml and apm.yml to <metadir> (and not to <dir>).
Documentation for apm unpack reflects the lockfile-is-metadata contract that the implementation now honors.
Three-layer plan (for cross-link visibility)
microsoft/apm (this issue): align apm unpack with its documented contract. Root cause.
This is acceptable as a quarantined, single-repo fix because we control the copy. It is not the right shape for upstream gh-aw or for apm-action; those layers get the structural fix above. The workaround becomes a documented no-op once microsoft/apm-action#26 ships.
PR-controlled primitive shadowing in pr-review-panel-style workflows (separate higher-severity finding): once this fix lets the workflow succeed, gh-aw's checkout_pr_branch.cjs can replace trusted primitives (e.g. .github/skills/apm-review-panel/SKILL.md) with the PR's versions. Independent of this issue; will file separately against gh-aw with a proper mitigation proposal once the fix chain above lands.
Summary
apm unpackwritesapm.lock.yamlandapm.ymlinto the output directory, even though the CLI's own documentation states these are bundle metadata, not output. This contract violation cascades up the stack:microsoft/apm-action's restore mode dumps the lockfile intoworking-directory(=${{ github.workspace }}by default) -> dirties tracked files -> any subsequentgit checkoutfails (see v1.5: restore mode must install apm CLI so bundles are unpacked via 'apm unpack' (not raw tar fallback) apm-action#26).github/gh-aw'sshared/apm.mdtemplate, which uses the action, then crashes anypull_request_targetworkflow whose triggering PR modifiesapm.lock.yaml(see feat(audit): close audit-blindness gap for local .apm/ content (#887) #889).Originating failure: https://github.com/microsoft/apm/actions/runs/24883083247/job/72856352586?pr=889
Originating scenario (plain English)
An agentic workflow built on the documented gh-aw template
shared/apm.mdruns in two phases on a PR:main, downloads the APM bundle (built earlier withisolated: 'true'into/tmp/gh-aw/apm-workspace), then callsmicrosoft/apm-actionin restore mode to deploy primitives into the workspace. As a side-effect, the action writesapm.lock.yamlandapm.ymlback into the workspace.git checkout -B <branch> origin/pr-head. Git refuses because the workspace is dirty.When the PR also modifies
apm.lock.yaml(which is exactly what every dependency-related PR does), the writes collide and the checkout aborts. The agent never starts.Root cause:
apm unpackcontract violationapm unpack's documented contract states thatapm.lock.yamlis bundle metadata and is not copied to the output directory. The implementation does the opposite: it writes the full bundle contents — includingapm.lock.yamlandapm.yml— into the output directory. Anything depending onapm unpack(most notablymicrosoft/apm-action) inherits the bug.Proposed fix: align CLI with its own contract
Goal:
apm unpack <bundle> --output <dir>writes only what the documented contract says it should: agent primitives under.github/{skills,agents,instructions,prompts}/andapm_modules/.apm.lock.yamlandapm.ymlare not written to<dir>.Behavior change
<dir>/.github/{skills,agents,instructions,prompts}/<dir>win, mirroringdiscover_primitives_with_dependenciespriority)<dir>/apm_modules/<dir>/apm.lock.yaml<dir>/apm.yml<dir>)--metadata-dir <path>flag (optional) writesapm.lock.yaml+apm.ymlto a separate caller-controlled pathWhy no-clobber for primitives
Today,
unpacker.py:37-39documents: "If a local file has the same name as a bundle file, the bundle file wins (overwrite)." This contradicts APM's own discovery priority (discover_primitives_with_dependencies: local primitives have highest priority). Aligning unpack's collision behavior with discovery's collision behavior makes the model consistent end-to-end.Why no opt-in flag
Callers who relied on
apm.lock.yamlbeing written to the output dir were relying on undocumented behavior that contradicts the CLI's own contract. Treat as aFixedbullet in CHANGELOG, not a breaking-flag dance.Migration
apm unpack; workspace stays clean.shared/apm.md: bump action pin to@v2once it ships. No template change beyond the version bump (v1.5: restore mode must install apm CLI so bundles are unpacked via 'apm unpack' (not raw tar fallback) apm-action#26 covers the migration plan).shared/apm.md: keep the temporary post-restoregit checkout -- . 2>/dev/null || trueworkaround untilmicrosoft/apm-action@v2is available; then drop it and re-import gh-aw'sshared/apm.mdupstream.apm unpack: those who genuinely need the lockfile post-unpack add--metadata-dir <path>.Acceptance criteria
apm unpack <bundle> --output <dir>does not writeapm.lock.yamlorapm.ymlinto<dir>.apm unpack <bundle> --output <dir>with a pre-existing tracked primitive (e.g.<dir>/.github/agents/foo.agent.md) does not overwrite it; the local file wins.apm unpack <bundle> --output <dir> --metadata-dir <metadir>writesapm.lock.yamlandapm.ymlto<metadir>(and not to<dir>).apm unpackreflects the lockfile-is-metadata contract that the implementation now honors.Three-layer plan (for cross-link visibility)
apm unpackwith its documented contract. Root cause.working-directorystops getting dirtied on restore. Every action consumer (gh-aw users, raw GH Actions users, anything) gets the fix transparently.shared/apm.mdpin tomicrosoft/apm-action@v2once available. One-line change. No template gymnastics.Temporary mitigation (already shipping)
While the CLI + action fixes are in flight, microsoft/apm's own copy of
shared/apm.mdcarries this single-line workaround after the Restore step:This is acceptable as a quarantined, single-repo fix because we control the copy. It is not the right shape for upstream gh-aw or for
apm-action; those layers get the structural fix above. The workaround becomes a documented no-op once microsoft/apm-action#26 ships.References
.github/workflows/shared/apm.md,.github/workflows/*.lock.yml(regenerated),CHANGELOG.mdsrc/apm_cli/...(apm unpack implementation, tests, docs)Out of scope
pr-review-panel-style workflows (separate higher-severity finding): once this fix lets the workflow succeed, gh-aw'scheckout_pr_branch.cjscan replace trusted primitives (e.g..github/skills/apm-review-panel/SKILL.md) with the PR's versions. Independent of this issue; will file separately against gh-aw with a proper mitigation proposal once the fix chain above lands.