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 --global — hackathon-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.
Summary
PR #1184 introduced the pattern
target: ${{ github.aw.import-inputs.target || 'all' }}inshared/apm.md. The|| 'all'fallback defeats gh-aw's compile-time substitution of${{ github.aw.import-inputs.* }}, so consumer-suppliedwith: target: copilotis silently ignored — the lock.yml keeps the unresolved expression and the runtime env var ends up empty (which apm then treats asall, triggeringcopilot-coworkinstall attempts that fail withrequires --global).Reproduction
Consumer workflow:
After
gh aw compile(v0.71.5 and v0.72.0 both reproduce):Root cause
gh-aw's substitution regex (
pkg/parser/import_field_extractor.go:922@ v0.72.0):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 anywaygh-aw's
applyImportSchemaDefaultsFromFrontmatteralready injects schema-declared defaults into the inputs map before substitution. With the schema PR #1184 already added:…and
target: ${{ github.aw.import-inputs.target }}(no fallback), the behavior is exactly what|| 'all'was meant to express:with: target: copilottarget: copilotwith:(target omitted)target: all(from schema default)Proposed fix
One-line change in
shared/apm.md:Schema default
allis 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: copilotwhen consumer provides it;target: allwhen omitted).Evidence
|| 'all'(current shared/apm.md): apm install fails because cowork install needs--global— hackathon-white-pig-8/zava-storefront run 25495022790target: copilot(proves substitution path is the only blocker): run 25495232796 — green end-to-end (apm install 11s, agent 7m11s, full pipeline succeeds)|| 'all') compiles to literaltarget: copilot: confirmed against gh-aw v0.72.0Optional parallel fix in gh-aw
Make
importInputsExprRegextolerant of GHA-idiom|| <fallback>so consumers can keep using the familiar pattern. Happy to file that ongithubnext/gh-awseparately — 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-
allmalfunction (separate but related)While debugging, surfaced what looks like a second upstream bug in apm: when
target=allis passed, apm attempts to installcopilot-coworkand fails with'copilot-cowork' target requires --global. Butsrc/apm_cli/core/target_detection.py:202explicitly documents thatEXPERIMENTAL_TARGETS(incl.copilot-cowork) is "NOT included in theparse_target_arg('all')expansion" — and indeednormalize_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.