Skip to content

shared/apm.md: drop || 'all' fallback on target (gh-aw substitution incompatible with GHA-idiom fallbacks) #1185

@danielmeppiel

Description

@danielmeppiel

Summary

PR #1184 introduced the pattern target: ${{ github.aw.import-inputs.target || 'all' }} in shared/apm.md. The || 'all' fallback defeats gh-aw's compile-time substitution of ${{ github.aw.import-inputs.* }}, so consumer-supplied with: target: copilot is silently ignored — the lock.yml keeps the unresolved expression and the runtime env var ends up empty (which apm then treats as all, triggering copilot-cowork install attempts that fail with requires --global).

Reproduction

Consumer workflow:

imports:
  - path: shared/apm.md
    with:
      packages: ["org/repo/plugins/foo#v1.0.0"]
      target: copilot   # ← ignored

After gh aw compile (v0.71.5 and v0.72.0 both reproduce):

# lock.yml
- name: APM Install
  uses: microsoft/apm-action@v1.7.2
  with:
    packages: '["org/repo/plugins/foo#v1.0.0"]'           # ← substituted (array, bare expression)
    target: ${{ github.aw.import-inputs.target || 'all' }} # ← NOT substituted

Root cause

gh-aw's substitution regex (pkg/parser/import_field_extractor.go:922 @ v0.72.0):

var importInputsExprRegex = regexp.MustCompile(
    `\$\{\{\s*github\.aw\.import-inputs\.([a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)?)\s*\}\}`)

Matches only bare ${{ github.aw.import-inputs.X }}. The || 'all' inside the brackets makes the regex not match → no substitution → unresolved expression survives into lock.yml → empty env at runtime.

This explains the asymmetry seen in the wild: AW_APM_PACKAGES: ${{ github.aw.import-inputs.packages }} (bare) gets substituted; target: ${{ github.aw.import-inputs.target || 'all' }} (with fallback) does not.

Why || 'all' is redundant anyway

gh-aw's applyImportSchemaDefaultsFromFrontmatter already injects schema-declared defaults into the inputs map before substitution. With the schema PR #1184 already added:

import-schema:
  target:
    type: string
    default: all

…and target: ${{ github.aw.import-inputs.target }} (no fallback), the behavior is exactly what || 'all' was meant to express:

Consumer After substitution
with: target: copilot target: copilot
with: (target omitted) target: all (from schema default)

Proposed fix

One-line change in shared/apm.md:

-target: ${{ github.aw.import-inputs.target || 'all' }}
+target: ${{ github.aw.import-inputs.target }}

Schema default all is already declared by PR #1184, no other change needed. Verified locally against gh-aw v0.72.0: lock.yml then carries the literal substituted value (target: copilot when consumer provides it; target: all when omitted).

Evidence

  • Smoke run with || 'all' (current shared/apm.md): apm install fails because cowork install needs --globalhackathon-white-pig-8/zava-storefront run 25495022790
  • Smoke run with vendor-patched literal target: copilot (proves substitution path is the only blocker): run 25495232796 — green end-to-end (apm install 11s, agent 7m11s, full pipeline succeeds)
  • Local repro that the elegant fix (drop || 'all') compiles to literal target: copilot: confirmed against gh-aw v0.72.0

Optional parallel fix in gh-aw

Make importInputsExprRegex tolerant of GHA-idiom || <fallback> so consumers can keep using the familiar pattern. Happy to file that on githubnext/gh-aw separately — but the apm-side fix is sufficient and aligns with intent (schema defaults exist for exactly this purpose; the || belt-and-suspenders just made the suspenders cut the belt).

Bonus: cowork-in-all malfunction (separate but related)

While debugging, surfaced what looks like a second upstream bug in apm: when target=all is passed, apm attempts to install copilot-cowork and fails with 'copilot-cowork' target requires --global. But src/apm_cli/core/target_detection.py:202 explicitly documents that EXPERIMENTAL_TARGETS (incl. copilot-cowork) is "NOT included in the parse_target_arg('all') expansion" — and indeed normalize_target_list("all") returns {vscode, claude, cursor, opencode, codex, gemini}, no cowork. So cowork is being enumerated by some other code path. Worth a separate issue — happy to file with the same evidence if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    In Progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions