Skip to content

fix: [CI-24318]: preserve multiline action output values - #28

Open
abhijeet-harness-io wants to merge 1 commit into
drone-plugins:mainfrom
abhijeet-harness-io:CI-24318-preserve-multiline-outputs
Open

fix: [CI-24318]: preserve multiline action output values#28
abhijeet-harness-io wants to merge 1 commit into
drone-plugins:mainfrom
abhijeet-harness-io:CI-24318-preserve-multiline-outputs

Conversation

@abhijeet-harness-io

@abhijeet-harness-io abhijeet-harness-io commented Aug 19, 2026

Copy link
Copy Markdown

Summary

Fixes CI-24318. GitHub Actions whose declared outputs contain multiline values (for example the changelog from mathieudutour/github-tag-action@v6.2 when the repo has multiple commits since the last tag) currently lose all lines after the first when the plugin copies outputs into Harness's \$DRONE_OUTPUT file. Downstream Harness steps that reference those outputs then see truncated or empty values, and features like automated GitHub Release creation fail.

Root cause

utils/workflow.go setOutputVariables generated a single echo "key=value" > \$DRONE_OUTPUT command per plugin run:

cmd += fmt.Sprintf("%s=\${{ steps.%s.outputs.%s }}\n", outputVar, prevStepId, outputVar)
cmd = fmt.Sprintf("echo \"%s\" > %s", cmd, outputFile)

When any value spans multiple lines, only the first line ends up on the key=value line. Remaining lines become bare lines with no =. Harness's delegate output reader (github.com/harness/godotenv) parses the file line by line as key=value and drops the bare lines, leaving the pipeline with a truncated value.

Fix

Emit each output as a single line in the form key=__B64__<base64 payload>. Harness's delegate reader already has a __B64__ prefix escape hatch (see lite-engine/pipeline/runtime/common.go) that base64-decodes the payload transparently before handing the environment map to the pipeline. Multiline values, values containing double quotes, dollar signs, backticks, and backslashes all round-trip cleanly because the payload is opaque to godotenv.

Per output variable, the writer now emits:

IFS= read -r -d '' __val <<'HARNESS_EOF_<random>' || true
\${{ steps.<prevStep>.outputs.<name> }}
HARNESS_EOF_<random>
__val="\${__val%\$'\n'}"
__b64=\$(printf '%s' "\$__val" | base64 | tr -d '\n')
echo "<name>=__B64__\$__b64" >> \$DRONE_OUTPUT

Every line has a reason:

  • IFS= read -r -d '' __val <<'MARKER'. Ingests the substituted value into a bash variable. Quoted marker means bash performs no expansion on the value. Top-level heredoc (not inside \$(cat <<'EOF' ... EOF)) avoids a bash 3.2 parser bug where a " inside the heredoc body confuses the outer \$(...) scanner. That bug is real on macOS's default shell and could surface in any runsOn image that ships bash 3.2.
  • || true. read -d '' returns non-zero when it never finds the null delimiter, which is always the case here. The variable is still populated.
  • __val="\${__val%\$'\n'}". A heredoc appends a trailing \n after the last content line. read captures that trailing \n. This one line strips it so values do not gain a phantom trailing newline.
  • printf '%s' "\$__val" | base64 | tr -d '\n'. printf '%s' avoids adding its own newline. tr -d '\n' removes GNU base64's default 76-column line wrapping. Portable across coreutils and BusyBox unlike base64 -w 0.
  • echo "<name>=__B64__\$__b64" >> outputFile. One line per variable, appended after the file has been truncated at the top of the writer.

A random 8-byte hex EOF marker per plugin run makes marker collisions with value content vanishingly unlikely.

Reader side needs no change. The __B64__ decode logic already exists.

Why not the GitHub-style heredoc format

The obvious first design was to use name<<MARKER ... MARKER blocks (the same format GitHub Actions itself uses in \$GITHUB_OUTPUT). I tried it. It writes a well-formed file but Harness's delegate reader is godotenv, which understands only key=value and quoted string forms. On the first line name<<MARKER godotenv logs Can't separate key from value and abandons the whole file, so every declared output resolves to null downstream, not just multiline ones. Base64 keeps every value on one line, sidesteps that entirely, and uses an escape hatch the reader already supports.

Test plan

Added to utils/workflow_test.go. All 5 tests pass locally with go test ./utils/....

  • TestSetOutputVariables_MultilineValueRoundTrip. 3 line changelog and single line tag both round-trip through the generated bash and a mock of the delegate's __B64__ decode.
  • TestSetOutputVariables_MultilineValuePreservesBlankLines. Values with blank lines and consecutive blank lines survive.
  • TestSetOutputVariables_ValuesWithSpecialChars. Values with ", \$, backticks, and backslashes survive.
  • TestSetOutputVariables. Writer emits per variable heredoc marker, __B64__ prefix, >> append, truncation of file at the top of the writer, and gates the step off when there are no output variables.
  • TestCreateWorkflowFile. Overall workflow YAML shape still valid.

End-to-end verification on Harness Cloud

Two pipelines with identical everything except the plugin image. Same input action (a deterministic composite that emits a fixed 3 line changelog and a single line tag).

Field Old plugin plugins/github-actions:latest Fixed plugin
changelog in downstream step truncated to first line all 3 lines present
wc -l on changelog 1 3
new_tag v0.1.1 v0.1.1
Delegate reader warning none (old shape parses without error, just truncates) none

Buggy run resolved <+steps.tag.output.outputVariables.changelog> to just * chore: bump internal dep C 57c830a. Fixed run resolved it to all three commit lines. New tag survived in both runs because it happens to be single line.

Relation to open PR #27

Independent bug, no overlap. PR #27 touches docker/Dockerfile.linux.amd64, plugin.go, utils/parse.go, and utils/parse_test.go. This PR only touches utils/workflow.go and utils/workflow_test.go. Two fixes can merge in either order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant