Course outline

GitHub Actions / Events, execution, and data boundaries / Lesson 6

Outputs, environment files, and file artifacts

Choose the correct data channel and preserve producer identity at every boundary.

About 4 min readContent reviewed
On this page

Lesson goals

  • Map step outputs to job outputs and downstream needs.
  • Distinguish scalar metadata from retained files and dependency caches.

Choose the channel by scope

A shell variable lasts within its process. GITHUB_ENV makes an environment value available to later steps in the same job, while GITHUB_OUTPUT declares step outputs that can be mapped to job outputs. Artifacts transfer retained files across jobs or beyond a run. Dependency caches accelerate reusable content under separate trust and lifecycle rules. Parcel’s candidate name fits a scalar output; its package belongs in artifact or release storage. A large report should not be squeezed into a scalar just to avoid choosing a proper file channel.

Write an output through the provided file

A step needs an id if later expressions refer to its outputs. The fragment below writes candidate to GITHUB_OUTPUT, maps it to a job output, and consumes it through needs in another job. It is incomplete because no event or enclosing workflow is supplied, and it never runs here. The synthetic value is a label, not a verified package identity. Production output must be derived from the actual produced candidate and validated before it influences a privileged decision.

yaml Step-to-job-to-needs output mapping
jobs:
  identify:
    runs-on: ubuntu-latest
    outputs:
      candidate: ${{ steps.label.outputs.candidate }}
    steps:
      - id: label
        run: printf "%s\n" "candidate=parcel-lab-r3" >> "$GITHUB_OUTPUT"
  report:
    needs: identify
    runs-on: ubuntu-latest
    steps:
      - env:
          PARCEL_CANDIDATE: ${{ needs.identify.outputs.candidate }}
        run: printf "%s\n" "$PARCEL_CANDIDATE"

GitHub supplies a unique output-file path to the step. The illustration assumes a suitable shell and does not create a package, attest its origin, or execute a workflow.

Respect text and multiline boundaries

Environment files are UTF-8 text with documented syntax. A single-line value is straightforward; arbitrary multiline content needs the supported delimiter form with a delimiter that cannot collide with the content, or a file artifact when arbitrary data is involved. Do not append contributor-controlled text as if it were a trusted key=value record. It can introduce extra assignments or malformed structure. PowerShell encoding behavior depends on the selected version, so use the documented shell-specific pattern rather than assuming every redirection produces the same bytes.

Files need explicit selection and retention

Uploading a path retains selected files under an artifact identity, subject to action version and platform retention rules. A downstream job must explicitly download the intended artifact. needs alone does not transfer its bytes. Use distinct names for matrix outputs and inspect hidden-file, permission, archive, and overwrite behavior in the selected action’s documentation. Tutorial major-version tags are mutable teaching references; an operational workflow should use reviewed immutable dependency identities and an update process. This course does not fabricate action commit pins or claim a hosted upload test.

Parse only a deliberately limited record

The complete local model parses two trusted single-line records and rejects duplicate keys. It is not a replacement for GitHub’s environment-file parser and does not support multiline syntax. The restriction makes the input contract explicit: values contain no newlines, keys are known, and duplicates are not silently accepted. Actual output consumers should validate expected fields and types. Never treat the mere existence of a downstream output as evidence that the upstream candidate or its authorization is valid.

python A restricted synthetic metadata parser
text = "candidate=parcel-lab-r3\nformat=plain\n"
result = {}
for line in text.splitlines():
    key, value = line.split("=", 1)
    if key in result:
        raise ValueError("duplicate key")
    result[key] = value
print(result["candidate"])
print(sorted(result))
Expected output
parcel-lab-r3
['candidate', 'format']

The code handles only the declared trusted single-line format. It performs no file I/O and cannot parse every legal GitHub environment-file representation.

Common errors and fixes

The producing step expects its new GITHUB_ENV value immediately.

Why it happens
The file updates later steps, not the current process environment.
How to fix it
Use the local variable now and the environment file for later steps.
Prevent it next time
Document the timing and scope of each transfer.

A downstream needs job cannot find package.zip.

Why it happens
A scalar dependency edge was mistaken for file transfer.
How to fix it
Upload and explicitly download the identified artifact.
Prevent it next time
Review artifact producer, name, paths, and retention.

Multiline user text creates extra output assignments.

Why it happens
Untrusted data was appended without a safe format contract.
How to fix it
Use supported bounded encoding or retain arbitrary content as a file.
Prevent it next time
Validate keys and avoid treating free text as control records.

Check your understanding

1. Which channel should carry a one-megabyte test report and a short candidate label?

Show worked solution

Retain the report as a selected file artifact with appropriate access and retention. Carry the short validated label through a step and job output when needed. Keep the relationship explicit so a label cannot accidentally identify another report or package.

2. A matrix produces three packages. Can all executions publish one shared scalar output reliably?

Show worked solution

Avoid relying on completion order to select a shared value. Name each package distinctly and aggregate an explicit manifest of expected combinations. Validate missing or failed combinations before downstream release decisions.

Key takeaways

  • Select data channels by scope, size, and lifecycle.
  • Output mapping does not authenticate the candidate it names.

Official references