If you’ve been working with GitHub Actions, you might have tried something like this to set an environment variable in one step and use it in another:
1jobs:
2 example:
3 runs-on: ubuntu-latest
4 steps:
5 - name: Set environment variable
6 run: |
7 export MY_VAR="Hello World"
8 echo "MY_VAR is set to $MY_VAR"
9
10 - name: Try to use the variable
11 run: echo "MY_VAR is $MY_VAR"
You may have also been surprised when the second step didn’t work as expected. The environment variable you set was not exported. While the first step correctly shows the value, the second step shows nothing. Let’s explore why this happens and how GitHub Actions actually handles environment variables.
How GitHub Actions executes jobs
To understand why export
doesn’t work across steps, we need to understand how GitHub Actions executes jobs. For each step in the job, Actions does a few things:
- Expression Interpolation
- First, GitHub evaluates any expressions in your workflow file (those
${{ }}
placeholders). These are replaced with their computed values before execution begins. The content is written verbatim, with no special treatment or escape sequences.
- File generation
- If the step is a
run
command, GitHub creates a temporary Bash script file containing your commands. This file is created in the$RUNNER_TEMP
directory. This ensures that the script will be cleaned up at the end of the job.
- Process creation
- A new process is spawned to execute this steps. This is done by creating and executing a command line based on the Action being executed. In the case of a
run
command, it invokes Bash to run the script file created in the previous step. The command line looks something like this:
1 /usr/bin/bash -e {0}
The
stdout
andstderr
streams of the process are captured for logging. This means that any output from your commands will be visible in the Actions logs. In addition, Actions will configure multiple environment variables for the process that include details about the environment, including special file paths such as$GITHUB_OUTPUT
and$GITHUB_ENV
(which will also point to temporary files for that step).- Step cleanup
- When the main process completes, Actions gathers the exit code and the temporary files. It then processes the special output files, parsing the contents to use for future steps. This also creates any step outputs and step statuses that might be needed for the GitHub expressions in the next steps.
Why export
doesn’t work
In standard bash/shell environments, the export
command marks variables to be inherited by child processes. Because of the way Actions works, each step runs in its own isolated process. When a step completes, its process terminates, and any environment variables set within that process die with it. Steps are sibling processes, not parent-child processes, so exports don’t propagate between them.
The correct approach: $GITHUB_ENV
If you want to make an environment variable available to another job, GitHub Actions provides a special file to do that. This path to this file is provided by the $GITHUB_ENV
environment variable. Writing values to this file will make the values available to all subsequent steps in the job.
1jobs:
2 example:
3 runs-on: ubuntu-latest
4 steps:
5 - name: Set environment variable
6 run: |
7 echo "MY_VAR=Hello World" >> $GITHUB_ENV
8 echo "MY_VAR is set to $MY_VAR"
9 echo "(And the variable will now be available in next steps!)"
10
11 - name: Use the variable
12 run: echo "MY_VAR is $MY_VAR"
At the end of each step, GitHub reads the contents of the GITHUB_ENV
file. Any variables defined there are added to the environment variables that will be used for subsequent steps. They are injected into the environment of all future step processes.
Conclusion
Understanding how GitHub Actions executes steps is key to working with environment variables effectively. It’s also an important part of understanding how to optimize your workflows. It’s also an important part of mastering the runners, especially when working with ARC! The isolation model allows each step to run and be managed independently. This is why there are so many options in the provided environment variables – GitHub gives you access to safely create and manage changes that will influence future process creation. I hope you’ll take some time to explore this further to understand how it works and learn about some of the other features that are available to you.
Happy DevOp’ing!