The orchestration of continuous integration and continuous delivery (CI/CD) within the GitHub ecosystem relies heavily on the ability to pass state, configuration, and identity data between different stages of a workflow. At the heart of this mechanism lie the built-in variables provided by GitHub Actions. These variables serve as the primary interface through which a workflow script interacts with the runner environment, the triggering event, and the GitHub API. For a developer to build a workflow, shell script, or build job of any merit, they must move beyond simple hardcoded values and instead leverage the dynamic nature of these environment variables.
The complexity of these variables manifests in two distinct layers: the default GitHub-provided variables and the operating system-specific environment variables provided by the runner images. While the standard documentation focuses on the former, the latter represents a vast array of system-level configurations that vary based on whether the runner is executing on Ubuntu, Windows, or macOS. Understanding the distinction between these layers is critical for creating portable and robust automation pipelines that can adapt to different execution environments without failure.
Default GitHub Actions Environment Variables
GitHub provides a set of default environment variables that are automatically injected into every step of a workflow. These variables are primarily used to provide context about the run, the repository, and the actor who triggered the event. A defining characteristic of these variables is that they are set by GitHub itself and are not defined within the workflow YAML, which means they are not accessible through the env context. Instead, they are accessed directly as environment variables in the shell or through specific context properties in the workflow syntax.
The standard set consists of 18 primary variables that are available across all runner types. These include:
- CI
- GITHUB_WORKFLOW
- GITHUBRUNID
- GITHUBRUNNUMBER
- GITHUB_ACTION
- GITHUB_ACTIONS
- GITHUB_ACTOR
- GITHUB_REPOSITORY
- GITHUBEVENTNAME
- GITHUBEVENTPATH
- GITHUB_WORKSPACE
- GITHUB_SHA
- GITHUB_REF
- GITHUBHEADREF
- GITHUBBASEREF
- GITHUBSERVERURL
- GITHUBAPIURL
- GITHUBGRAPHQLURL
The impact of these variables is profound for the stability of a build pipeline. For instance, using GITHUB_WORKSPACE ensures that an action uses the correct filesystem path regardless of the runner's internal directory structure, preventing the catastrophic failure of hardcoded paths. By utilizing these variables, developers create a "dense web" of connectivity between the GitHub API and the local shell execution, ensuring that the workflow knows exactly which commit it is testing (GITHUB_SHA) and which branch is being targeted (GITHUB_REF).
Detailed Analysis of Core Default Variables
To fully grasp the utility of these variables, one must examine their specific functions and the real-world consequences of their values during a runtime execution.
| Variable | Technical Description and Behavior |
|---|---|
| CI | Always set to true. This is used by many tools to detect if they are running in a CI environment versus a local development machine. |
| GITHUB_ACTION | The name of the action currently running, or the id of a step. If a script runs without an id, GitHub uses __run. For repeated actions, a suffix is added (e.g., __run_2 or actionscheckout2). |
| GITHUB_ACTIONS | Always set to true when GitHub Actions is executing. This allows developers to differentiate between local test runs and official CI runs. |
| GITHUB_ACTOR | The username or app name that initiated the workflow (e.g., octocat). |
| GITHUB_REPOSITORY | The full owner and repository name (e.g., owner/repo). |
| GITHUBACTIONPATH | Supported only in composite actions. It provides the absolute path to where the action is located, allowing the action to access other files in its own repository. |
| GITHUBACTIONREPOSITORY | For a step executing an action, this identifies the owner and repository of the action itself, such as actions/checkout. |
The GITHUB_ACTION variable is particularly nuanced. Because GitHub removes special characters and appends sequence numbers for repeated steps, it allows for granular logging and debugging. If a developer uses the same action multiple times, the incrementing suffix (like _2) enables the developer to pinpoint exactly which instance of an action caused a failure in a complex matrix build.
Immutable Nature and Variable Overwriting
A critical constraint in the GitHub Actions environment is the rule regarding the mutability of these variables. GitHub imposes strict restrictions on which variables can be modified by the user to maintain the integrity of the runner's internal state.
Variables prefixed with GITHUB_* and RUNNER_* are immutable. This means that if a developer attempts to redefine GITHUB_SHA within a shell script or a workflow env block, the original value set by GitHub will persist. The logic behind this is to ensure that the identity of the run remains consistent throughout the lifecycle of the job.
However, the CI variable is an exception. Currently, developers can overwrite the value of the CI variable. While this is possible today, GitHub does not guarantee that this capability will remain in the future. This creates a potential risk for workflows that rely on manipulating the CI flag to toggle specific tool behaviors, suggesting that developers should avoid relying on this overwrite if they require long-term stability.
OS-Specific Environment Variables and Runner Diversity
Beyond the 18 default variables, every runner image provides a comprehensive suite of environment variables inherent to the operating system distribution. These are not "GitHub variables" in the sense that they are not created by the Actions engine, but they are "Actions variables" in the sense that they are available for use within the workflow.
The number of available variables varies significantly by the image used:
- Ubuntu-latest: Provides over 60 additional variables beyond the default 18.
- Windows-latest: This image is the most expansive, offering approximately 120 environment variables.
- MacOS-latest: The count varies slightly compared to Ubuntu, depending on the specific version of the macOS image.
For example, the PATH variable is a standard issue on windows-latest, which is essential for the shell to locate executable binaries. On Windows runners, variables such as ALLUSERSPROFILE, ANDROID_HOME, and ANDROID_NDK_HOME are pre-configured (e.g., C:\ProgramData or C:\Program Files (x86)\Android\android-sdk), allowing mobile developers to run Android builds without manually configuring the SDK paths.
The impact of this variability means that a script written for ubuntu-latest may fail on windows-latest if it relies on a specific system variable that only exists in the Linux environment. To mitigate this, developers must treat the runner as a full operating system and not just a sterile execution environment.
Dynamic Inspection of the Environment
Because the full list of available variables is not exhaustively documented for every single minor version of every runner image, the most effective way for a developer to discover available variables is through a diagnostic workflow. By executing the env command (on Linux/macOS) or the equivalent in PowerShell (on Windows), the container is forced to print its entire environment state to the console.
To implement an environment inspection, a developer should follow these steps:
- Create a YAML workflow file configured to trigger on a push to the
mainormasterbranch. - Define three separate jobs, ensuring one is assigned to
ubuntu-latest, one towindows-latest, and one tomacos-latest. - Within each job, add a single step that executes the command to list variables.
- Execute the build and navigate to the job status window to view the full output.
The following configuration demonstrates the implementation of this inspection technique:
```yaml
name: Publish GitHub Actions Artifacts Example
on:
push:
branches: [ main ]
jobs:
github-actions-environment-variables-ubuntu:
runs-on: ubuntu-latest
steps:
- name: Ubuntu GitHub Actions environment variables List
run: env
github-actions-environment-variables-windows:
runs-on: windows-latest
steps:
- name: List of the GitHub Actions environment variables on Windows
run: env
github-actions-environment-variables-macos:
runs-on: macos-latest
steps:
- name: MacOs List of GitHub Actions environment variables
run: env
```
This approach allows the developer to see exactly what is available in their specific runtime, providing a level of certainty that static documentation cannot offer.
Context Mapping and the Relationship with env
There is a significant technical distinction between an environment variable and a context property in GitHub Actions. While GITHUB_REF is an environment variable available to the shell, ${{ github.ref }} is a context property used by the GitHub Actions expression evaluator.
The mapping between these two is nearly one-to-one. Most default environment variables have a corresponding property in the github context. This is critical because the env context in a workflow YAML does not contain the default GitHub variables. If a developer needs to use a default variable within a YAML expression (such as in an if conditional), they must use the context property rather than the environment variable name.
For example, while a shell script uses echo $GITHUB_REF, a workflow conditional must use:
yaml
if: ${{ github.ref == 'refs/heads/main' }}
This architectural split ensures that the workflow engine can evaluate conditions before the runner is even provisioned, while the runner itself has access to the variables via the standard OS environment.
Iterative Variable Access and Custom Implementations
A common challenge for advanced users is the need to iterate over all available variables and secrets to map them to external configurations, such as a deployment.yaml file. Currently, there is no built-in, native GitHub Actions method to "loop" through all environment variables or secrets dynamically.
Since secrets are masked and variables are injected into the environment of the runner, any attempt to programmatically list all variables must be done via shell commands like env or printenv. However, secrets are specifically designed to be hidden from the logs; therefore, any attempt to print all environment variables will result in the secrets appearing as ***. This security feature prevents the accidental leakage of sensitive data but also means that a purely dynamic "map all" approach to deployment expressions requires a structured naming convention or a predefined list of keys rather than a blind iteration of the environment.
Conclusion
The architecture of GitHub Actions variables is designed to provide a seamless bridge between the cloud-based orchestration of the workflow and the local execution of the runner. By providing a core set of 18 default variables, GitHub ensures that every single run has a consistent identity and context. The further expansion into OS-specific variables—ranging from the 60+ variables in Ubuntu to the 120 in Windows—allows these runners to function as full-fledged development environments capable of complex tasks like Android SDK management.
The critical takeaway for any DevOps professional is the necessity of understanding the immutability of GITHUB_* variables and the distinction between environment variables and context properties. The use of diagnostic workflows to dump the environment via the env command remains the only foolproof method for discovering the full breadth of available tools on a specific runner image. By moving away from hardcoded paths and utilizing the provided variable ecosystem, developers ensure their pipelines are portable, scalable, and resilient to the underlying infrastructure changes of the GitHub runner fleet.