Summary
In packages/cli-v3/src/utilities/githubActions.ts, the helper builds entries.map(([k, v]) => ${k}=${v}).join("\n") and appendFileSyncs it to $GITHUB_ENV / $GITHUB_OUTPUT. .join("\n") only separates entries — there's no trailing newline — so the last KEY=value is left on an unterminated line. The canonical @actions/core always terminates each entry with os.EOL.
Impact
Both call sites (deploy.ts) end their outputs block with needsPromotion=..., a documented, downstream-consumed Action output. Because the line is unterminated, a subsequent append to the same job-scoped file (by the runner or another step) can concatenate onto needsPromotion=... (and symmetrically the first key can merge onto a prior unterminated tail), producing malformed/merged values that break promotion branching. It's intermittent — GitHub often inserts its own newline and single-consumer runs are fine — which is why it has survived.
Suggested fix
Terminate each entry with a newline, matching @actions/core — e.g. append a trailing \n (or build with .map(... => ${k}=${v}\n).join("")). Apply to both the GITHUB_ENV and GITHUB_OUTPUT writes. Scope to the missing terminator; multiline values (the ghadelimiter heredoc form) are a separate concern.
Summary
In
packages/cli-v3/src/utilities/githubActions.ts, the helper buildsentries.map(([k, v]) =>${k}=${v}).join("\n")andappendFileSyncs it to$GITHUB_ENV/$GITHUB_OUTPUT..join("\n")only separates entries — there's no trailing newline — so the lastKEY=valueis left on an unterminated line. The canonical@actions/corealways terminates each entry withos.EOL.Impact
Both call sites (
deploy.ts) end their outputs block withneedsPromotion=..., a documented, downstream-consumed Action output. Because the line is unterminated, a subsequent append to the same job-scoped file (by the runner or another step) can concatenate ontoneedsPromotion=...(and symmetrically the first key can merge onto a prior unterminated tail), producing malformed/merged values that break promotion branching. It's intermittent — GitHub often inserts its own newline and single-consumer runs are fine — which is why it has survived.Suggested fix
Terminate each entry with a newline, matching
@actions/core— e.g. append a trailing\n(or build with.map(... =>${k}=${v}\n).join("")). Apply to both the GITHUB_ENV and GITHUB_OUTPUT writes. Scope to the missing terminator; multiline values (theghadelimiterheredoc form) are a separate concern.