When engine.env values are written as multi-line YAML block scalars (e.g. >- with extra-indented continuation lines), goccy/go-yaml preserves the embedded newlines in the parsed string. Both emission sites in engine_helpers.go emitted env vars as single-line key: value pairs, so those newlines broke the compiled .lock.yml. PR #18017 added engine.env support but didn't account for this case.
Frontmatter Content
engine:
id: copilot
env:
COPILOT_GITHUB_TOKEN: >-
${{ secrets.GH_AW_PAT_1 != '' && secrets.GH_AW_PAT_1 ||
secrets.GH_AW_PAT_2 != '' && secrets.GH_AW_PAT_2 ||
secrets.GH_AW_PAT_3 != '' && secrets.GH_AW_PAT_3 ||
secrets.GH_AW_PAT_4 != '' && secrets.GH_AW_PAT_4 ||
secrets.GH_AW_PAT_5 }}
Expected Result
engine:
env:
COPILOT_GITHUB_TOKEN: >-
${{ secrets.GH_AW_PAT_1 != '' && secrets.GH_AW_PAT_1 ||
secrets.GH_AW_PAT_2 != '' && secrets.GH_AW_PAT_2 ||
secrets.GH_AW_PAT_3 != '' && secrets.GH_AW_PAT_3 ||
secrets.GH_AW_PAT_4 != '' && secrets.GH_AW_PAT_4 ||
secrets.GH_AW_PAT_5 }}
Actual Result
generated lock file is not valid YAML: [166:5] non-map value is specified
163 | run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
164 | env:
165 | COPILOT_GITHUB_TOKEN: ${{ secrets.GH_AW_PAT_1 != '' && secrets.GH_AW_PAT_1 ||
> 166 | secrets.GH_AW_PAT_2 != '' && secrets.GH_AW_PAT_2 ||
^
167 | secrets.GH_AW_PAT_3 != '' && secrets.GH_AW_PAT_3 ||
168 | secrets.GH_AW_PAT_4 != '' && secrets.GH_AW_PAT_4 ||
169 | secrets.GH_AW_PAT_5 }}
170 |
✗ compilation failed
Workaround
engine:
id: copilot
env:
# We cannot use line breaks in this expression as it leads to a syntax error in the compiled workflow
COPILOT_GITHUB_TOKEN: ${{ secrets.GH_AW_PAT_1 != '' && secrets.GH_AW_PAT_1 || secrets.GH_AW_PAT_2 != '' && secrets.GH_AW_PAT_2 || secrets.GH_AW_PAT_3 != '' && secrets.GH_AW_PAT_3 || secrets.GH_AW_PAT_4 != '' && secrets.GH_AW_PAT_4 || secrets.GH_AW_PAT_5 }}
Prototype Fix
I have a branch and a PR within my fork that should resolve this issue. The changes were authored via Copilot Coding Agent, human- and ai-reviewed, with tests running in a CCA session.
Changes
appendEnvVarLine helper — new shared function replacing direct fmt.Sprintf at both env var emission sites. Trims trailing \n (from | block scalars), then emits single-line values inline via yamlStringValue (unchanged path) or multi-line values as a YAML literal block scalar (|).
FormatStepWithCommandAndEnv and GenerateMultiSecretValidationStep — both now delegate to appendEnvVarLine.
Example
A five-PAT precedence chain written as a multi-line expression in the workflow frontmatter:
engine:
id: copilot
env:
COPILOT_GITHUB_TOKEN: >-
${{ secrets.GH_AW_PAT_1 != '' && secrets.GH_AW_PAT_1 ||
secrets.GH_AW_PAT_2 != '' && secrets.GH_AW_PAT_2 ||
secrets.GH_AW_PAT_3 != '' && secrets.GH_AW_PAT_3 ||
secrets.GH_AW_PAT_4 != '' && secrets.GH_AW_PAT_4 ||
secrets.GH_AW_PAT_5 }}
The extra-indented continuation lines are treated as "more-indented" by the YAML spec, so the newlines are not folded to spaces — they land in the parsed string. The compiled output now emits this correctly as a literal block scalar:
env:
COPILOT_GITHUB_TOKEN: |
${{ secrets.GH_AW_PAT_1 != '' && secrets.GH_AW_PAT_1 ||
secrets.GH_AW_PAT_2 != '' && secrets.GH_AW_PAT_2 ||
...
secrets.GH_AW_PAT_5 }}
When
engine.envvalues are written as multi-line YAML block scalars (e.g.>-with extra-indented continuation lines),goccy/go-yamlpreserves the embedded newlines in the parsed string. Both emission sites inengine_helpers.goemitted env vars as single-linekey: valuepairs, so those newlines broke the compiled.lock.yml. PR #18017 addedengine.envsupport but didn't account for this case.Frontmatter Content
Expected Result
Actual Result
Workaround
Prototype Fix
I have a branch and a PR within my fork that should resolve this issue. The changes were authored via Copilot Coding Agent, human- and ai-reviewed, with tests running in a CCA session.
Changes
appendEnvVarLinehelper — new shared function replacing directfmt.Sprintfat both env var emission sites. Trims trailing\n(from|block scalars), then emits single-line values inline viayamlStringValue(unchanged path) or multi-line values as a YAML literal block scalar (|).FormatStepWithCommandAndEnvandGenerateMultiSecretValidationStep— both now delegate toappendEnvVarLine.Example
A five-PAT precedence chain written as a multi-line expression in the workflow frontmatter:
The extra-indented continuation lines are treated as "more-indented" by the YAML spec, so the newlines are not folded to spaces — they land in the parsed string. The compiled output now emits this correctly as a literal block scalar: