Skip to content
Putting technology to work.
Insights to guide decisions and action.

Search articles

Actions job outputs: empty values are not set, and the pitfalls with re-runs and matrices

Table of contents · 6 items

You pass a version number decided in an earlier job, or a check of whether anything changed, to a later job. In GitHub Actions, you use the job's outputs and the needs context. But if you do not know how empty values and reruns are handled, mix-ups can happen, such as "a job that should not have deployed ran" or "I reran it, but it tagged with an old version number."

On November 6, 2026, text stating these two points explicitly was added to GitHub's documentation repository (github/docs). In this article, we confirm the behavior from the added text and the source code of the Actions runner (actions/runner), and summarize how to write workflows.

Outputs with empty values are "not set"

The following sentence was added to the section that defines job outputs.

If an output's value evaluates to an empty string, that output is not set. If step debug logging is enabled, the debug message Skip output 'OUTPUT-NAME' since it's empty appears in the job log.

In the runner source code (src/Runner.Worker/JobExtension.cs) as well, after evaluating the job outputs, if a value is empty it records Skip output '…' since it's empty as a debug message and skips that output. Next comes processing that skips values that may contain secrets, with the warning Skip output '…' since it may contain secret..

The key point is that the message for empty values is a debug message. Step debug logs appear when you set ACTIONS_STEP_DEBUG to true as a secret or variable in the repository containing the workflow, or when you select "Enable debug logging" on rerun (in GitHub CLI, when you add --debug to gh run rerun). Because nothing appears in the normal run log, it is hard to notice that an output was skipped for being empty.

From a later job, you cannot distinguish an output that was not set because it was empty from an output whose value could not be obtained in the first place. The page explaining contexts says that referencing a property that does not exist evaluates to an empty string. needs.build.outputs.version is an empty string both when it was not set because it was empty and when the value could not be obtained because of a typo in the step's id. If you mistype id, the job output expression itself evaluates to an empty string, so the runner should treat it the same as "skipped because empty" (this is inferred from the source; we have not tested it).

In matrix jobs, empty values do not overwrite

The warning about running the same job multiple times with a matrix was also updated.

Actions does not guarantee the order in which matrix jobs run. Make output names unique; otherwise, they are overwritten by the value of the last job that ran and set the output. Because outputs with empty values are not set, they do not overwrite values set by other matrix jobs.

For example, if only one of three jobs emits changed=true and the other two emit empty values, the two empty ones do not overwrite, so true remains. But if you change the other jobs to emit changed=false, whether true or false remains depends on the run order. When receiving a single decision from a matrix, the reliable approaches are to "set the output only when the condition is met," or to give the output a separate name for each matrix value and check them together in the later job using toJSON(needs.<job>.outputs) or similar. The latter follows the same pattern as the official documentation's example: it defines three outputs, output_1 to output_3, and each matrix job writes only the output for its own number. The other two are empty, so they are not set and no overwriting occurs.

On rerun, outputs of jobs outside the rerun are reused

The following text was added to the rerun page.

When you rerun failed jobs or a specific job, the jobs that depend on it are also rerun. Jobs not included in the rerun keep their outputs from the previous attempt, and newly run jobs can use those outputs.

The same page also says that reruns use the privileges of the person who initially ran the workflow, use the same GITHUB_SHA (commit) and GITHUB_REF as the original event, that a single run can be rerun up to 50 times, and that reruns are limited to within 30 days of the initial run.

Reuse becomes a problem when outputs are built from "external state at that point in time." For example, suppose a build job creates an output from that day's date or from the latest number queried from an external registry, and the deploy job fails. If you rerun only the deploy job, the build job is not rerun, so the output stays at its previous value. If the output is determined solely by the commit contents, there is no problem because the commit is the same. But if the external state has changed, processing continues with the previous value.

Diagram of how GitHub Actions handles job outputs. If a value is an empty string, the output is not set and nothing appears in the normal log. With debug logging enabled, Skip output since it's empty is recorded. From a later job, both empty and nonexistent outputs look like empty strings. When only failed jobs are rerun, earlier jobs outside the rerun are not rerun, and their previous outputs are used as-is.

How to write workflows that avoid mix-ups

What follows are the editorial team's suggestions based on the sources above. They are not the results of trying reruns in an actual repository.

Do not branch on whether a value is empty. A check like "do not deploy if the output is empty" gives the same result when there is a typo in the step's id or when the command that produces the value fails. Always write out some value for decisions, such as true / false, and in the later job use a condition like needs.build.outputs.deploy == 'true' that proceeds only when the value matches what you expect.

Fail the step that produces the value if it is empty. For outputs that should always have a value, such as a version number, check that the value is not empty before writing it to $GITHUB_OUTPUT, and stop with exit code 1 if it is. This stops the run closer to the cause than noticing only after an empty value reaches a later job.

- id: version
  run: |
    v="$(node -p "require('./package.json').version || ''")"
    if [ -z "$v" ]; then echo "version is empty" >&2; exit 1; fi
    echo "value=$v" >> "$GITHUB_OUTPUT"

On the job side, pass it along by writing something like version: ${{ steps.version.outputs.value }} in outputs. || '' is added because if version is missing from package.json, node -p outputs the string undefined, which slips past the empty check. On a local macOS machine (bash 3.2.57, Node.js 22.23.4), we ran only the contents of this run with bash -e and confirmed that it stops with exit code 1 when version is missing, when it is empty, and when package.json is missing, and that value=1.2.3 is written out when version is 1.2.3. Without || '', value=undefined was written out and the check passed when version was missing. We have not run it on Actions.

Regenerate outputs that depend on external state inside the job that uses them. If you need values that depend on the date or an external latest number to be correct even after a rerun, either fetch the value inside the job that uses it, or adopt a practice of choosing "Re-run all jobs."

When investigating, rerun with debug logging enabled. On the rerun screen, you can choose whether to enable debug logging. Checking the Skip output message tells you whether a value was empty or was masked as a secret. However, as noted above, a typo in id should also be recorded as empty, so this message alone does not tell you why the value became empty. Also keep in mind the output size limits (1MB per job, 50MB total per run).

We covered pinning workflow dependencies in our article on pinning tag references with actions.lock, and detecting keys that appear in logs in our article on secret scanning in Actions logs.

Scope of verification

On November 9, 2026, we checked the diff of github/docs commit 6abed3d (November 6, 2026, "Document that empty job outputs aren’t set and that jobs not re-run keep their outputs"), the source of the pages on job outputs, reruns, debug logging and contexts as of the time of checking (8cd1075), and JobExtension.cs on the main branch of actions/runner (77e8fff, November 9, 2026) (desk research). On November 11, 2026, we confirmed the same text on the published docs.github.com pages and in the latest source (7c3387d), and also confirmed that the same processing exists in JobExtension.cs on actions/runner main (5f626f1) and in the latest release, v2.338.2 (October 26, 2026). We have not confirmed which version is currently running on GitHub-hosted runners. On the same day we also looked at the GitHub Changelog (October 28 to November 11) and found no announcement about job outputs or reruns. We did not try running or rerunning workflows on GitHub. For the YAML in this article, we ran only the contents of run locally.

GleamHub's development, AI and automation consultations cover CI/CD reviews and how to build deployment decisions. The approach depends on your repository structure and operating setup, so please reach out via Contact.

References

Share this articleXFacebook
Kakeru Suzuki

Fascinated by the possibilities of technology, has had a deep interest in programming and digital art since student days

Turn this article's theme into your company's next step

Concrete steps forward for your organization.

We organize your desired architecture, legacy systems, and operational requirements to formulate your next steps toward execution.

  • Desired architecture
  • Integration with existing environments
  • Operational requirements
Consult on development & operations initiatives

You can consult with us from the initial conceptual stage. Details from this article will be carried over to the inquiry form.

Receive the latest articles by email