fix(shared/apm): re-shape single-artifact download to expected layout#1051
fix(shared/apm): re-shape single-artifact download to expected layout#1051danielmeppiel merged 2 commits intomainfrom
Conversation
actions/download-artifact (>=v5) silently overrides 'merge-multiple: false' and flattens contents directly into 'path/' whenever exactly one artifact matches the pattern. Our 'Validate downloaded bundles match matrix manifest' step assumed the per-artifact subdir always exists, so any single-group consumer (the common case: one credential group, one packed bundle) hits 'missing APM bundles (group did not pack successfully): apm-<id>' even though the bundle downloaded and verified successfully. This was latent until #1033 unblocked apm-prep -- previously apm-prep itself was failing on the malformed jq input, so the agent job never got far enough to run validation. With prep fixed, every single-group PR run of pr-review-panel and triage-panel now fails at the validation step. Fix: add a normalisation step that, when matrix has exactly one group, re-creates the expected '${prefix}apm-<id>/' subdir and moves the flattened bundle file(s) into it. No-op for multi-group matrices where download-artifact already produces per-artifact subdirs. Validation script is unchanged. Refs: actions/download-artifact src/download-artifact.ts -- the 'isSingleArtifactDownload || mergeMultiple || artifacts.length === 1' check that triggers the flatten. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR fixes a workflow failure caused by actions/download-artifact flattening the download directory when exactly one artifact matches the pattern, by normalizing the bundle layout back into the per-artifact subdirectory expected by downstream validation.
Changes:
- Add a “Normalise bundle layout” step that re-creates the expected
${prefix}apm-<id>/directory when the matrix contains exactly one group. - Recompile/refresh the locked workflow YAMLs and actions lockfile to reflect the shared workflow update.
Show a summary per file
| File | Description |
|---|---|
| .github/workflows/shared/apm.md | Adds a normalization step to restore expected per-group directory layout for single-artifact downloads. |
| .github/workflows/pr-review-panel.lock.yml | Propagates the normalization step into the compiled/locked workflow. |
| .github/workflows/triage-panel.lock.yml | Propagates the normalization step into the compiled/locked workflow. |
| .github/aw/actions-lock.json | Refreshes the action lock mappings as part of gh aw compile output. |
Copilot's findings
- Files reviewed: 4/4 changed files
- Comments generated: 3
| group_dir="/tmp/gh-aw/apm-bundles/${ARTIFACT_PREFIX}apm-${group_id}" | ||
| if [ ! -d "$group_dir" ]; then | ||
| mkdir -p "$group_dir" | ||
| find /tmp/gh-aw/apm-bundles -mindepth 1 -maxdepth 1 ! -path "$group_dir" -exec mv {} "$group_dir/" \; |
There was a problem hiding this comment.
mv can treat source paths beginning with - as options (e.g., a file named --target-directory=...), which can break the step (and in some contexts be abused). Use mv -- so all artifact-derived paths are treated as operands (e.g., -exec mv -- '{}' "$group_dir/" \;). This should be applied in the shared workflow so the compiled lock files inherit the fix.
| find /tmp/gh-aw/apm-bundles -mindepth 1 -maxdepth 1 ! -path "$group_dir" -exec mv {} "$group_dir/" \; | |
| find /tmp/gh-aw/apm-bundles -mindepth 1 -maxdepth 1 ! -path "$group_dir" -exec mv -- {} "$group_dir/" \; |
| ARTIFACT_PREFIX: ${{ needs.activation.outputs.artifact_prefix }} | ||
| EXPECTED_MATRIX: ${{ needs.apm-prep.outputs.matrix }} | ||
| name: Normalise bundle layout (single-artifact flatten workaround) | ||
| run: "set -euo pipefail\n# actions/download-artifact (>=v5) flattens contents directly into `path/`\n# whenever exactly one artifact matches the pattern, ignoring\n# `merge-multiple: false`. Re-shape into the per-group subdir layout so\n# downstream validation sees a stable structure regardless of matrix size.\nexpected_count=$(echo \"$EXPECTED_MATRIX\" | jq '.group | length')\nif [ \"$expected_count\" -eq 1 ]; then\n group_id=$(echo \"$EXPECTED_MATRIX\" | jq -r '.group[0].id')\n group_dir=\"/tmp/gh-aw/apm-bundles/${ARTIFACT_PREFIX}apm-${group_id}\"\n if [ ! -d \"$group_dir\" ]; then\n mkdir -p \"$group_dir\"\n find /tmp/gh-aw/apm-bundles -mindepth 1 -maxdepth 1 ! -path \"$group_dir\" -exec mv {} \"$group_dir/\" \\;\n fi\nfi\n" |
There was a problem hiding this comment.
This workflow executes mv on artifact-derived filenames; if any entry begins with -, mv may interpret it as an option. Update the find ... -exec mv ... invocation to include mv -- so operands are unambiguous.
| run: "set -euo pipefail\n# actions/download-artifact (>=v5) flattens contents directly into `path/`\n# whenever exactly one artifact matches the pattern, ignoring\n# `merge-multiple: false`. Re-shape into the per-group subdir layout so\n# downstream validation sees a stable structure regardless of matrix size.\nexpected_count=$(echo \"$EXPECTED_MATRIX\" | jq '.group | length')\nif [ \"$expected_count\" -eq 1 ]; then\n group_id=$(echo \"$EXPECTED_MATRIX\" | jq -r '.group[0].id')\n group_dir=\"/tmp/gh-aw/apm-bundles/${ARTIFACT_PREFIX}apm-${group_id}\"\n if [ ! -d \"$group_dir\" ]; then\n mkdir -p \"$group_dir\"\n find /tmp/gh-aw/apm-bundles -mindepth 1 -maxdepth 1 ! -path \"$group_dir\" -exec mv {} \"$group_dir/\" \\;\n fi\nfi\n" | |
| run: "set -euo pipefail\n# actions/download-artifact (>=v5) flattens contents directly into `path/`\n# whenever exactly one artifact matches the pattern, ignoring\n# `merge-multiple: false`. Re-shape into the per-group subdir layout so\n# downstream validation sees a stable structure regardless of matrix size.\nexpected_count=$(echo \"$EXPECTED_MATRIX\" | jq '.group | length')\nif [ \"$expected_count\" -eq 1 ]; then\n group_id=$(echo \"$EXPECTED_MATRIX\" | jq -r '.group[0].id')\n group_dir=\"/tmp/gh-aw/apm-bundles/${ARTIFACT_PREFIX}apm-${group_id}\"\n if [ ! -d \"$group_dir\" ]; then\n mkdir -p \"$group_dir\"\n find /tmp/gh-aw/apm-bundles -mindepth 1 -maxdepth 1 ! -path \"$group_dir\" -exec mv -- {} \"$group_dir/\" \\;\n fi\nfi\n" |
| ARTIFACT_PREFIX: ${{ needs.activation.outputs.artifact_prefix }} | ||
| EXPECTED_MATRIX: ${{ needs.apm-prep.outputs.matrix }} | ||
| name: Normalise bundle layout (single-artifact flatten workaround) | ||
| run: "set -euo pipefail\n# actions/download-artifact (>=v5) flattens contents directly into `path/`\n# whenever exactly one artifact matches the pattern, ignoring\n# `merge-multiple: false`. Re-shape into the per-group subdir layout so\n# downstream validation sees a stable structure regardless of matrix size.\nexpected_count=$(echo \"$EXPECTED_MATRIX\" | jq '.group | length')\nif [ \"$expected_count\" -eq 1 ]; then\n group_id=$(echo \"$EXPECTED_MATRIX\" | jq -r '.group[0].id')\n group_dir=\"/tmp/gh-aw/apm-bundles/${ARTIFACT_PREFIX}apm-${group_id}\"\n if [ ! -d \"$group_dir\" ]; then\n mkdir -p \"$group_dir\"\n find /tmp/gh-aw/apm-bundles -mindepth 1 -maxdepth 1 ! -path \"$group_dir\" -exec mv {} \"$group_dir/\" \\;\n fi\nfi\n" |
There was a problem hiding this comment.
Same as in triage-panel.lock.yml: add -- to the mv invocation so filenames starting with - cannot be misinterpreted as flags, preventing avoidable job failures on edge-case artifact contents.
| run: "set -euo pipefail\n# actions/download-artifact (>=v5) flattens contents directly into `path/`\n# whenever exactly one artifact matches the pattern, ignoring\n# `merge-multiple: false`. Re-shape into the per-group subdir layout so\n# downstream validation sees a stable structure regardless of matrix size.\nexpected_count=$(echo \"$EXPECTED_MATRIX\" | jq '.group | length')\nif [ \"$expected_count\" -eq 1 ]; then\n group_id=$(echo \"$EXPECTED_MATRIX\" | jq -r '.group[0].id')\n group_dir=\"/tmp/gh-aw/apm-bundles/${ARTIFACT_PREFIX}apm-${group_id}\"\n if [ ! -d \"$group_dir\" ]; then\n mkdir -p \"$group_dir\"\n find /tmp/gh-aw/apm-bundles -mindepth 1 -maxdepth 1 ! -path \"$group_dir\" -exec mv {} \"$group_dir/\" \\;\n fi\nfi\n" | |
| run: "set -euo pipefail\n# actions/download-artifact (>=v5) flattens contents directly into `path/`\n# whenever exactly one artifact matches the pattern, ignoring\n# `merge-multiple: false`. Re-shape into the per-group subdir layout so\n# downstream validation sees a stable structure regardless of matrix size.\nexpected_count=$(echo \"$EXPECTED_MATRIX\" | jq '.group | length')\nif [ \"$expected_count\" -eq 1 ]; then\n group_id=$(echo \"$EXPECTED_MATRIX\" | jq -r '.group[0].id')\n group_dir=\"/tmp/gh-aw/apm-bundles/${ARTIFACT_PREFIX}apm-${group_id}\"\n if [ ! -d \"$group_dir\" ]; then\n mkdir -p \"$group_dir\"\n find /tmp/gh-aw/apm-bundles -mindepth 1 -maxdepth 1 ! -path \"$group_dir\" -exec mv -- {} \"$group_dir/\" \\;\n fi\nfi\n" |
APM Review Panel Verdict: REJECT
Required before merge (1 item)
Nits (11 items, skip if you want)
CEO arbitrationThe panel is unanimous that this PR addresses a real and disruptive CI regression: actions/download-artifact v5+ silently flattens single-artifact downloads, breaking the expected per-group directory layout for downstream validation steps. The normalisation fix is mechanically correct and the procedural shell approach is sound for a sequential CI runner. All five active panelists found no architectural objections and the non-security nits are minor quality improvements -- better tracing in CI logs, a link to the upstream issue, CHANGELOG hygiene -- none of which block correctness. The single blocking finding comes from Supply Chain Security: the PR removes the All remaining nits are non-blocking. The path-sanitization note on group_id (guarding against traversal sequences before shell interpolation) is the highest-value nit and worth folding in before or alongside the required fix, but it does not independently block the PR. Dissent resolved: No inter-panelist disagreements. All active panelists agreed on the severity classification of every finding. Supply Chain Security's required finding was not contested by any other panelist. Growth/positioning note: The OSS Growth Hacker correctly identifies that silent single-group panel failures hit first-time contributors hardest -- the exact cohort whose retention matters most. A contributor who opens their first PR, sees "missing APM bundles", and gets no explanation will assume they broke something and disengage. A one-sentence reliability note in the next release post or CHANGELOG entry closes that loop and signals to the community that the project actively tracks and fixes contributor-path failures. Low effort, high trust signal. Per-persona findings (full)Python ArchitectNo mermaid diagrams applicable -- purely procedural shell script with no class or module boundaries. Flow: read EXPECTED_MATRIX -> count groups via jq -> if count==1 and group_dir absent, mkdir + find/mv. Guard is correct; TOCTOU not a concern in a sequential CI runner. Required: None. Nits:
CLI Logging ExpertRequired: None. Nits:
DevX UX ExpertRequired: None. Nits:
Supply Chain Security ExpertRequired:
Nits:
Auth ExpertInactive -- PR modifies only CI/CD workflow YAML files (.github/workflows/shared/apm.md, pr-review-panel.lock.yml, triage-panel.lock.yml) and actions-lock.json; no auth-sensitive files (auth.py, token_manager.py, github_downloader.py, etc.) were touched. OSS Growth HackerRequired: None. Nits:
Side-channel note (forwarded to CEO): This bug created a silent false failure for any single-group consumer of the panel/triage workflows -- exactly the path a first-time contributor walks when opening a PR. A contributor who sees "missing APM bundles" on their first PR will assume they broke something and disengage. Worth a sentence in the next release post under reliability wins. Verdict computed deterministically: 1 required finding across 5 active panelists. APPROVE iff N == 0. Push a new commit to clear this verdict label automatically.
|
…changelog - Restore github/gh-aw-actions/setup-cli@v0.71.2 entry in actions-lock.json (still referenced by .github/workflows/agentics-maintenance.yml; the prior recompile dropped it because pr-review-panel and triage-panel don't use it). - Sanitise group_id with strict allowlist (^[A-Za-z0-9_-]+$) before interpolating into the shell path, as defence-in-depth even though apm-prep produces a sanitised id today. - Guard jq '.group | length' against null with '.group // []'. - Link upstream actions/download-artifact source in the comment so future maintainers can verify the workaround is still needed. - CHANGELOG entry under [Unreleased] -> Fixed referencing #1051. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Resolved panel review findings in f231989: Required (Supply Chain Security):
Adopted nits:
Set aside (with rationale):
Recompiled both lock files. Pushing should auto-clear |
TL;DR
actions/download-artifact(since v5) silently overridesmerge-multiple: falseand flattens contents directly intopath/whenever exactly one artifact matches the pattern. Our validation step inshared/apm.mdassumed the per-artifact subdir always exists, so any single-group consumer (the common case: one credential group, one packed bundle) fails withmissing APM bundles (group did not pack successfully): apm-<id>even though the bundle downloaded and verified successfully.Repro: https://github.com/microsoft/apm/actions/runs/25124365062/job/73633628506?pr=1039
Why latent until now
#1033 fixed an earlier failure in
apm-prep(Go-slice → JSON serialisation forjq --argjson). Before that fix, the agent job never reached the validation step. With prep working, every single-group PR run ofpr-review-panelandtriage-panelnow hits this latent bug.Fix
Add a normalisation step before validation: when the matrix has exactly one group, re-create the expected
${prefix}apm-<id>/subdir and move the flattened bundle file(s) into it. No-op for multi-group matrices (download-artifact already produces per-artifact subdirs in that case). Validation script unchanged.Reference
actions/download-artifactv8.0.1 source:Files
shared/apm.md— new "Normalise bundle layout" steppr-review-panel.lock.yml,triage-panel.lock.yml— recompiledactions-lock.json— refreshed bygh aw compile