# actrun runs GitHub Actions workflows on your own machine

> actrun is a local GitHub Actions runner written in MoonBit that executes workflow YAML with a gh-compatible CLI, plus a linter, a job dependency visualiser and a flow cache for external orchestrators. It suits a developer who wants to reproduce a CI failure locally, and it is not a substitute for the hosted runner when a workflow depends on GitHub's own services.

**mizchi/actrun** — GitHub Actions compatible local runner

- Repository: https://github.com/mizchi/actrun
- Stars: 673 · Forks: 15
- Language: MoonBit
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/mizchi-actrun

## A local runner that tries to keep GitHub's release contract

actrun is a local GitHub Actions runner built with MoonBit, and its purpose is to run and debug GitHub Actions workflows locally with a gh-compatible command line. The readme is explicit about the design constraint, saying the project keeps its release contract as close as possible to existing GitHub Actions semantics. Workflow YAML and action metadata stay on a GitHub-compatible surface, while WASM support is treated as a self-hosted runner optimization rather than a separate concept. The project keeps a written boundary for this, pointing at a public API document for the contract boundary and at an architecture decision record for the rationale. That is an unusual amount of care to take for a local tool, and it is the thing to check first when evaluating it, because a local runner that silently diverges from the hosted semantics is worse than no local runner at all. The core claim is that the same workflow file that runs on GitHub can be run on a developer machine, which is what makes the tool worth the setup time.

## Six ways to install, which tells you how the project expects to be used

The install section is a good map of the project's intended audiences, because each route implies a different kind of user. The shortest path needs nothing installed at all:

```bash
npx @mizchi/actrun workflow run .github/workflows/ci.yml
curl -fsSL https://raw.githubusercontent.com/mizchi/actrun/main/install.sh | sh
docker run --rm -v "$PWD":/workspace -w /workspace ghcr.io/mizchi/actrun workflow run .github/workflows/ci.yml
```

Those three cover the try-it-now case, the Linux and macOS shell install, and the containerised case that leaves your machine clean. The remaining routes are for people with an opinionated toolchain. There is a global npm install, two Nix options that run without installing and install into a profile respectively, a MoonBit-native install through moon install, and a from-source build that clones the repository and runs moon build src/cmd/actrun --target native. The Nix path has its own section with a flake that exposes an overlay, a default package and a development shell, plus a non-flake default.nix that fetches a tarball of the main branch. The variety is not gratuitous. A tool that runs other people's build commands has to be installable in whatever environment the project already uses, or people will not use it.

## The workflow subcommand is the entry point for everything else

The command surface is organised around three groups. The workflow commands list the files in .github/workflows/ and run one of them. The run commands deal with the results of previous runs, with subcommands to list runs, view a run summary, view a run as JSON, watch a run until it completes, read all logs, read the log of a specific task, and download all artifacts. The analysis commands do two things that have nothing to do with running a job. The linter type checks expressions and detects dead code, and the visualiser renders the workflow job dependency graph, with output as ASCII art in a terminal, as Mermaid text for Markdown, as Mermaid with step subgraphs at a higher detail level, or as an SVG image with a selectable theme. The linter is the feature that changes how you use the tool, because a workflow that references an undefined context or calls a function with the wrong number of arguments will fail in CI. The documented diagnostics are errors rather than warnings for undefined-context, wrong-arity and unknown-function, which is the right severity for problems that only appear at run time on GitHub. Rules can be suppressed individually with a repeatable ignore flag, which is important because the rules are still new enough to need an escape hatch.

## An init command writes the config, and flags always win over the file

Configuration is generated rather than hand written, with actrun init producing an actrun.toml in the current directory. The generated file is commented throughout, which makes it readable as documentation:

```toml
# Workspace mode: local, worktree, tmp, docker
workspace_mode = "local"

# Skip actions not needed locally
local_skip_actions = ["actions/checkout"]

# Trust all third-party actions without prompt
trust_actions = true

# Nix integration: "auto" (force), "off" (disable), or empty (auto-detect)
nix_mode = ""

# Additional nix packages
nix_packages = ["python312", "jq"]

# Container runtime: docker, podman, container, lima, nerdctl
container_runtime = "docker"
```

The set of knobs tells you what the tool worries about. The workspace mode decides whether the run happens in your working tree, in a separate git worktree, in a temporary directory or inside a container, and the worktree and temporary modes can include uncommitted changes when include_dirty is set. The trust_actions flag is the security-relevant one, since a third party action runs code on your machine, and the default is to prompt. There is also an override table that swaps an action for a local command, an affected-file pattern table that maps file globs to workflows, and a local_context table for pinning the repository, ref and actor. The readme states the precedence rule plainly, that command line flags always override the actrun.toml settings, and it says the GitHub context values are auto-detected from the local git repository when possible, with local_context only needed when you want to pin them.

## The flow cache exists for an orchestrator actrun does not run

The one feature whose motivation is not obvious from the name is the flow cache. You use a --flow-cache-store path together with one or more --flow-signature flags of the form job-or-task=fingerprint, and that is how you exchange task cache state with the embedded bitflow planner. The point of the dry run mode is that actrun workflow run with --dry-run and --json emits a flow_cache.plan payload carrying per-task hit and miss information, so an external orchestrator can decide what to execute. A normal run writes the fingerprints of successful tasks back to the same store, and both the plan and the writeback result are persisted under run.json as flow_cache.plan and flow_cache.writeback. This is the part of the design that suggests who the tool is for. A developer debugging one workflow does not need a cache exchange protocol. A team running a fleet of pipelines with an external scheduler does, and this gives actrun a role as the executor inside a larger system rather than as a replacement for GitHub Actions. The same reasoning explains the skip-action flag, which lets a local run omit actions that are not needed on a developer machine, such as a checkout of the repository you are already standing in.

## MoonBit in the core, Node and WASM on the edges

The implementation choices are unusual enough to be worth understanding, because they constrain what the tool can do. The primary language is MoonBit, and the repository has a moon.mod file, a justfile with MoonBit tasks, and a src/ directory. The justfile shows the development loop: a default target that checks and tests, formatting targets using moon fmt and moon fmt --check, a type check target that runs moon check with warnings denied for a configurable target, and separate snapshot targets for updating and verifying golden files, including ones that download GitHub Actions example snapshots and compare them against local output. That last set of targets is the strongest evidence about the project's intent, since comparing your output against the real hosted runner's output is how you keep the local semantics honest. The npm package exists as a distribution channel rather than as the implementation, with a bin entry pointing at a bundled dist/actrun.js and a library export carrying type declarations, and the engines field requires Node 18 or newer. The Dockerfile shows the build matrix: MoonBit only supports x86_64, so the build stage is forced to linux/amd64, the native binary and a JavaScript bundle are both produced, and the runtime stage uses the native binary on amd64 but a Node.js shim on arm64.

## Conclusion

actrun is worth adopting for a team that debugs CI on a laptop and wants the same workflow file to run locally, especially since the linter catches undefined context and wrong arity before a workflow ever reaches a runner. It is the wrong tool for a job that depends on GitHub hosted services such as pull request events or the cache service, and it is a poor substitute for a hosted runner when a workflow needs a real Linux image rather than a local machine. Verify first which parts of your workflow actually execute under actrun, that the local_context values you pin match the event you are reproducing, and that the container runtime you select is one your machine can run. The last push was on 2026-10-03 and the newest release is v0.32.0 from 2026-09-18.

## FAQ

### How do I run a GitHub Actions workflow locally with actrun?

Use actrun workflow run .github/workflows/ci.yml, or prefix it with npx @mizchi/actrun if you do not want to install anything. Add --dry-run to see the execution plan without running anything.

### What can actrun lint in a workflow file?

The linter type checks expressions and detects dead code, reporting undefined-context, wrong-arity and unknown-function as errors. Individual rules can be suppressed with a repeatable ignore flag such as --ignore W001.

### How does actrun decide which workspace to run in?

The workspace_mode setting in actrun.toml accepts local, worktree, tmp or docker, and the CLI flag overrides the file. Worktree and temporary modes can include uncommitted changes when include_dirty is enabled.

### Is running third-party actions locally with actrun safe?

Third-party actions execute code on your machine, and the generated actrun.toml exposes a trust_actions flag that skips the prompt for every action. The default is to prompt,

### What is the flow cache in actrun for?

It exchanges task cache state with the embedded bitflow planner through --flow-cache-store and --flow-signature flags, so an external orchestrator can plan a run. A dry run with --json emits the hit and miss plan, and a normal run writes successful task fingerprints back.

## Sources

- [Issues](https://github.com/mizchi/actrun/issues)
- [License: Apache-2.0](https://github.com/mizchi/actrun/blob/main/LICENSE)
- [mizchi/actrun on GitHub](https://github.com/mizchi/actrun)
- [README](https://github.com/mizchi/actrun/blob/main/README.md)
- [Releases](https://github.com/mizchi/actrun/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mizchi-actrun
