Last updated 2026-05-07

Runner Overview

The runner is a standalone Node.js server that reads .github/workflows/*.yml, evaluates GitHub Actions expressions, resolves marketplace actions, and executes workflows locally. It exposes a REST API and an SSE stream so the CLI, the web dashboard, or any HTTP client can trigger, monitor, and inspect workflow runs in real time.

Why this exists

FeatureGitHub Actionsact@stax/runner
REST API for trigger / monitorNoNoYes
SSE real-time streamingNoNoYes
Sandboxed execution (worktree / CoW)N/ANoYes
Secrets with encryptionOrg-level.envSOPS + age from git, plus local mirror
Deployment ledgerCloud historyNoLocal JSONL + optional GitHub issue mirror
Matrix with fail-fastYesPartialYes
Reusable workflows (local + remote)YesNoYes
Composite + Node + Docker actionsYesDocker onlyYes
Built-in shims (no Docker for shell)N/AN/AYes

When to use it

  • Instead of GitHub Actions: when you want fast iteration on CI workflows without pushing commits, or you need local-only execution (airgapped, private, cost savings).
  • Instead of act: when you need a REST API, SSE streaming, sandboxed execution, reusable workflow support, or Node.js action support without Docker.

Starting the runner

# Via the CLI (preferred)
stax runner start --workspace /path/to/your/project --port 4800
# Directly (development)
cd platform/runner
RUNNER_WORKSPACE=/path/to/your/project npm run dev

The runner banner confirms its endpoints:

Local GitHub Actions runner started
API: http://localhost:4800/api
Events: http://localhost:4800/api/events
Health: http://localhost:4800/health
Workspace: /path/to/your/project

Trigger your first workflow

# List workflows the runner discovered
curl http://localhost:4800/api/workflows
# Trigger one
curl -X POST http://localhost:4800/api/runs \
-H 'Content-Type: application/json' \
-d '{"workflow": "ci"}'
# With workflow_dispatch inputs
curl -X POST http://localhost:4800/api/runs \
-H 'Content-Type: application/json' \
-d '{"workflow": "deploy", "inputs": {"environment": "staging"}}'

Stream events

curl -N http://localhost:4800/api/events
event: ping
data: {}
event: run.started
data: {"runId":"a1b2c3d4","run":{...}}
event: step.log
data: {"runId":"a1b2c3d4","jobId":"build","stepNumber":2,"line":"npm ci\n"}
event: job.completed
data: {"runId":"a1b2c3d4","jobId":"build","job":{"conclusion":"success",...}}

What works out of the box

  • run: steps in bash, sh, python, or pwsh.
  • uses: actions/checkout@v4 — shimmed to a no-op since the workspace is already mounted.
  • uses: actions/upload-artifact@v4 / download-artifact — copies to / from $WORKSPACE/.artifacts/.
  • uses: actions/cache@v4 — filesystem cache at ~/.local/share/local-runner/cache/.
  • Setup actions (setup-python, setup-java, setup-go, setup-ruby, setup-dotnet, setup-gradle, setup-xcode, flutter-action) — shimmed to detect system-installed tools.
  • Matrix strategies with include, exclude, fail-fast, max-parallel.
  • Job dependencies via needs:, conditional if:, reusable workflows (local ./ and remote org/repo@ref), composite + Node + Docker actions, services via docker compose.
  • $GITHUB_OUTPUT, $GITHUB_ENV, $GITHUB_PATH file protocols and the ::set-output, ::error, ::warning, ::group, ::add-mask workflow commands.
  • Repo-backed SOPS + age secrets can be synced into the runner mirror and consumed through ${{ secrets.NAME }}.
  • Jobs with tracked environment: names are written to a local deployment ledger and can be mirrored to a GitHub issue.

What's not supported

  • concurrency groups
  • permissions / OIDC tokens
  • environment protection rules (environment names are parsed for logging only)
  • workflow_run trigger
  • schedule (cron) trigger

Continue reading