iTranslated by AI
Execute GitHub Actions Workflows as Batch Scripts
ghx (GitHub eXecute) allows you to run GitHub Actions workflows in your local environment (Windows, macOS, Linux).
You can use workflows with the same feeling as "just running npm run ... in any environment!"
Supported Workflows
Supported workflows follow a flow like 👇:
-
usesfor reproducing the local environment -
matrixconfigurations - Executing cross-platform build tools via
run
This is for workflows that don't rely heavily on complex Bash scripts or handling credentials/secrets.
How It Works
Modern development language toolchains are essentially cross-platform. Therefore, many people have likely thought that as long as the matrix is expanded, run can be used directly as a batch script. ghx makes that possible.
By running the following in a repository that has the workflow shown at the beginning...
dotnet tool install -g ghx # Install ghx
ghx dry test --once # Expand only one matrix combination (dry run)
👇 It looks like this. Exactly as you'd imagine.
echo ============================================================================
echo job 'dry': matrix count=12
echo ============================================================================
echo ----------------------------------------------------------------------------
echo dry: configuration=Debug mode= once=
echo ----------------------------------------------------------------------------
dotnet run -c Debug -f net10.0 --project ./src -- \
dry \
\
\
sample
echo ============================================================================
echo job 'run': matrix count=6
echo ============================================================================
echo ----------------------------------------------------------------------------
echo run: configuration=Debug runner=ubuntu-latest
echo ----------------------------------------------------------------------------
dotnet run -c Debug -f net10.0 --project ./src -- run sample
dotnet run -c Debug -f net10.0 --project ./src -- new workflow-template
Also handles things like removing redirects to
$GITHUB_STEP_SUMMARY, etc.
Windows Support
But! Usually, runners use ubuntu-latest most of the time.
Therefore, we need to bridge the minor differences between Bash and CMD.
- End-of-line escape
\→^ - Positional parameters
$0-9→%0-9(which are rarely used anyway) -
sleep N→timeout /t N /nobreak >nul(for some reason, it stabilizes after waiting a few seconds!)
In addition:
- Executing
.bat/.cmdwithin a batch script causes it to exit regardless of success or failure- Since
dnxornpxare not.exefiles, executing them prevents subsequent commands from running
- Since
- There is no way to reproduce the same behavior as
bash -e(exit immediately on error) from GitHub Actions in standard batch scripts
To address this, we need to perform a conversion like CALL <original command> || EXIT 1.
👇 Result (for Windows)
@ECHO OFF
echo ============================================================================
echo job 'dry': matrix count=12
echo ============================================================================
echo ----------------------------------------------------------------------------
echo dry: configuration=Debug mode= once=
echo ----------------------------------------------------------------------------
CALL dotnet run -c Debug -f net10.0 --project ./src -- ^
dry ^
^
^
sample || CALL :ERROR
echo ============================================================================
echo job 'run': matrix count=6
echo ============================================================================
echo ----------------------------------------------------------------------------
echo run: configuration=Debug runner=ubuntu-latest
echo ----------------------------------------------------------------------------
CALL dotnet run -c Debug -f net10.0 --project ./src -- run sample || CALL :ERROR
CALL dotnet run -c Debug -f net10.0 --project ./src -- new workflow-template || CALL :ERROR
GOTO :EOF
:ERROR
ECHO.
ECHO ======= ERROR OCCURRED =======
ECHO.
EXIT 310
It's a bit quirky, but it works perfectly.
Creating New Workflows
Few people can remember the GitHub Composite action syntax from scratch.
ghx new <new-workflow-name>
By running this, you can create a best-practice-oriented C# workflow that uses SHA pinning.
name: workflow-template
on:
#push:
# branches: [ "main" ]
#pull_request:
# branches: [ "main" ]
#workflow_call:
workflow_dispatch:
jobs:
workflow-template:
#strategy:
# matrix:
# configuration: [Debug, Release]
runs-on: ubuntu-latest # tip: runs-on can use ${{ matrix.<name> }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
with:
dotnet-version: 10.x.x
- run: |
echo Template created with 'ghx'
Since you basically just need to list the build tool calls in the final run, it's better to build it for GitHub compatibility from the start.
In projects involving multiple languages or those with unique requirements, simple commands provided by development environments like dotnet build, cargo build, or go build often aren't enough. It's useful to use ghx test or ghx build to create cross-platform scripts.
Composite Action Support Table
The basic style is "covering with operations if it doesn't work."
| Feature | Support Status | Notes |
|---|---|---|
workflow_call trigger |
✅ Full | Main use case. workflow_dispatch also works |
| Input definitions | ✅ Full | Type declarations are ignored. Default values are required when referencing |
| Matrix strategy | ✅ Full | Expanded as Cartesian product. --once flag available |
| Multiple jobs | ✅ Full | Executed sequentially. No parallelism or environment isolation |
| Run steps | ✅ Full | Only run: is extracted; uses: is ignored |
| Placeholder expressions | ⚠️ Partial | Only ${{ inputs.* }} and ${{ matrix.* }} are supported |
| Bash scripts | ✅ Full | Default shell. Becomes cross-platform through conversion |
| Custom shells | ❌ None | Specifying shell: results in an error |
| Runner | ⚠️ Limited | Only ubuntu-latest runs. Others trigger a warning |
| Positional parameters | ⚠️ Limited | Converts $0-$9 to %0-%9 when outputting to CMD |
| Sleep command | ✅ Full | Converts sleep N to TIMEOUT /T N /NOBREAK >nul on Windows |
👇 Other details
Reusable Workflows
By adding workflow_call to on, you can call the workflow from another workflow.
Therefore, by keeping workflows that only handle build tool calls small and calling them from more complex workflows (such as those involving authentication), it becomes easier to support both local and GitHub environments with ghx.
Restrictions when reusing workflows:
- Workflows cannot be organized into subfolders, etc.
-
usesandstepscannot be used together.
jobs:
reusable-workflow:
uses: ./.github/workflows/workflow.yml # Complete the job using only uses
# Link subsequent jobs
subsequent-job:
needs: reusable-workflow
if: success()
runs-on: ubuntu-latest
...
In the if condition, you can use success(), failure(), always(), cancelled(), and so on.
By utilizing outputs and other features, you should also be able to receive workflow artifacts via Artifacts.
name: Call a reusable workflow and use its outputs
on:
workflow_dispatch:
jobs:
job1:
uses: ./.github/workflows/called-workflow.yml
job2:
needs: job1
if: success()
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
# Download artifacts from the previous job
name: ${{ needs.job1.outputs.artifact_name }}
Conclusion
It helps save on private repository usage quotas, and being able to expand the matrix in a local environment is also quite convenient.
That's all. Thank you for reading.
Discussion