Run your GitHub Actions locally. Merge on the green check.
Baste runs the workflows you already have in a fresh Linux VM on your machine every time you git push, then posts each job to GitHub as a check branch protection accepts. No queue, no workflow rewrites, no servers.
npm install -g @weftsh/baste
No Node.js? Use the install script instead.
macOS on Apple Silicon, Linux, and Windows 11 via WSL2. Uses your GitHub CLI login.
$ git push To github.com:acme/web.git 4f1c2e9..a8d31b7 feature/login -> feature/login baste: running CI for a8d31b7 (feature/login) locally in run q7hz2m $ baste status q7hz2m ● running a8d31b7 feature/login just now push ✓ CI / lint 41s ✓ CI / build 1m02s ● CI / test (node 22) 1m18s running 'npm test' ● CI / test (node 24) 1m16s running 'npm test' → CI / e2e (windows) handed to GitHub
All checks have passed
On the pull request, before a hosted runner starts
-
Required
baste/CI/lint
Passed in 41s · baste logs q7hz2m
-
Required
baste/CI/build
Passed in 1m02s · baste logs q7hz2m
-
Required
baste/CI/test (node 22)
Passed in 2m04s · baste logs q7hz2m
-
Required
baste/CI/test (node 24)
Passed in 1m58s · baste logs q7hz2m
Runs the workflow files you already have
- actions/checkout
- actions/setup-node
- JavaScript actions
- composite actions
- Docker actions
- strategy.matrix
- needs:
- if:
- artifacts
Why Baste
Cloud CI is rented and queued. Your laptop is mostly idle.
Every push waits for a hosted runner, then bills you for the minutes. Local tools have only ever solved half of that: they run something on your machine, but either not your real workflow or not in a way GitHub will accept.
Baste does both. It runs the jobs in .github/workflows in a runner-like VM, and reports each one to GitHub as a check you can make required.
| act | gh-signoff | Baste | |
|---|---|---|---|
| Runs the jobs in your workflow files | ✓ | A script you choose | ✓ |
| Each job in a fresh, runner-like VM | Docker on your host | – | ✓ |
| Result shows up on the commit in GitHub | – | ✓ | ✓ |
Starts on git push, in the background |
You run it | You run it | ✓ |
How it works
Set it up once. Then just push.
No new workflow syntax, no runner to host, no change to how you work.
-
1
Install and run
baste initIt checks virtualization, your GitHub CLI login and that your token can write commit statuses, then installs a pre-push hook. If anything is missing, it says what and changes nothing.
-
2
Push like you always do
git pushreturns right away. Within seconds each local job shows as pending on the commit, and boots a fresh VM that checks out exactly what you pushed, not your working tree. -
3
Merge on the green check
Each job's check turns green or red with its duration and run id. Make the
baste/…checks required, and pull requests merge on a local pass.
Features
A local pass you can trust to predict GitHub's
Fidelity first: Baste runs what it can run faithfully, and hands the rest to GitHub before the run starts.
A fresh VM for every job
Each job boots a copy-on-write clone of a pinned Ubuntu 24.04 image, as the runner user with passwordless sudo and Docker. Nothing leaks between runs, and nothing depends on what's installed on your laptop.
Checks GitHub accepts
One commit status per job, with a stable name like baste/CI/test that works as a required check in branch protection and rulesets.
Never blocks a push
The hook starts the run in the background and returns. A newer push to the same branch cancels the older run.
Honest hand-offs
Windows, macOS and service-container jobs are listed as handed to GitHub before the run starts. Never half-run.
Secrets in your keychain
Secrets come from the macOS Keychain or Secret Service, never from GitHub. A missing one fails the job by name.
Live logs, one-command reruns
baste logs latest streams each step as it runs. baste rerun re-checks the same commit.
Insights that justify the switch
Time per step, the slowest steps, and how long the same workflow last took on GitHub-hosted runners, at the end of every run and in baste insights.
Zero infrastructure
Your machine does the work and GitHub is the only backend. Baste uses the token from gh auth token and never stores one. Logs stay on your machine.
Merge gates
Merge on a local pass. Two ways.
- A Required check Default
- Require the
baste/<workflow>/<job>checks thatbaste initlists, and make the GitHub-hosted versions optional. No workflow edits. - B The gate action Opt-in
- Add one job to a workflow. When Baste already passed the commit locally, the GitHub-hosted jobs skip and the workflow passes in seconds, saving the Actions minutes. With no local result, everything runs on GitHub as usual.
jobs: baste-gate: runs-on: ubuntu-latest outputs: skip: ${{ steps.gate.outputs.skip }} steps: - id: gate uses: weftsh/baste/gate@v1 test: needs: baste-gate if: needs.baste-gate.outputs.skip != 'true' runs-on: ubuntu-latest # ...your steps, unchanged
Runs where you work
A real VM on every major desktop
Linux jobs run locally on every host. Baste picks the VM backend for your machine.
Windows 11
WSL2
Firecracker inside WSL2, with nested virtualization turned on.
Runs locally
runs-on: ubuntu-*jobspushandpull_request- JavaScript, composite, Docker actions
- Matrices,
needs,if, outputs - Artifacts between jobs
- Step summaries and annotations
Handed to GitHub
- Windows and macOS jobs
- Self-hosted runners
services:andcontainer:- Deployments (
environment:) - OIDC (
id-token: write) - Reusable workflows
The full list is in the compatibility guide. Intel Macs aren't supported.
Get started
Your first local check in a few minutes
One binary. It needs the GitHub CLI, logged in with gh auth login.
- Free and open source under Apache-2.0
- No account, no server, no workflow changes
baste uninstallremoves the hook and statuses stop
# install
npm install -g @weftsh/baste
# or, without Node.js
curl -fsSL https://raw.githubusercontent.com/weftsh/baste/main/install.sh | sh
# in your repository
baste init
git push
baste status
baste logs latest
FAQ
Questions teams ask first
Do I need to change my workflows?
No. Baste reads the workflows at the commit you pushed and runs the ubuntu-* jobs as they are. The optional gate action is the only edit, and only if you want GitHub to skip jobs that already passed locally.
Does my code leave my machine?
Only the commit statuses go to GitHub. The checkout comes from your local repository, and logs, artifacts and insights stay on your machine. Jobs still reach the network the way they would on GitHub, for example to install dependencies.
What happens to jobs Baste can't run?
They're listed as handed to GitHub before the run starts and get no local status, so GitHub keeps running them as usual. A job that needs one of them is handed over too.
Can a teammate fake a green check?
Statuses are posted with each developer's own GitHub token, so anyone with write access could post one by hand, just as they could today. Baste is built for small, trusted teams. Signed attestations are on the roadmap; if you need separation of duties, keep CI on GitHub.
How is this different from act?
act runs workflows in Docker containers on your host, and its results stay on your machine. Baste runs each job in a fresh VM built like a hosted runner, starts on push, and reports to GitHub so the result can gate a merge.
What does it cost?
Nothing. Baste is open source under Apache-2.0 and runs on hardware you already have. Jobs that pass locally don't need to use GitHub Actions minutes.
Stop waiting on the queue.
Install Baste, run baste init, and your next push is checked on your own machine.