act
act is the second line of defence for GitHub Actions workflows,
behind the actionlint skill.
actionlint is static — it proves a workflow parses
and its expressions and types are sound.
act is dynamic — it runs the workflow locally in Docker
to see whether it actually works.
Use it as a proposal, not a gate:
once actionlint is clean on the staged workflows,
offer to run them through act and surface real failures —
but never block the commit on act.
Its local run can't cover everything,
and the local environment is fragile.
When in doubt, ask the operator
If anything is ambiguous —
above all, whether a failure is the workflow's fault
or just the local environment's —
ask the operator instead of guessing.
One question is cheaper than calling a broken workflow fine,
or scaring the operator about a failure that's only a missing local image.
This applies to every step below.
When this runs
- After actionlint reports clean on the staged workflow files —
don't spend Docker on a workflow the cheap static check already rejected.
- It's a proposal on every commit that touches a workflow,
not a requirement.
Offer the run; if the operator declines, skip it — the commit is not blocked.
Preconditions
The run needs a working Docker and a preconfigured image.
Check them first; if any is missing,
tell the operator, skip the run, and let the commit proceed —
never block over a precondition.
- Docker reachable —
docker info succeeds
(any provider: Docker Desktop, colima, or podman via podman info).
Missing binary or daemon down → act can't run; skip.
- A default runner image is configured.
On its first run act interactively asks for an image size,
and with no TTY it dies with
fatal EOF — so it must be set up beforehand.
The config lives in act's actrc
(~/.config/act/actrc, ~/.actrc,
or ~/Library/Application Support/act/actrc on macOS);
a configured one has a -P default line.
If none is set, ask the operator to run act once and pick Medium
(~500MB; Large is ~70GB, Micro is node-only) — don't guess an image.
If you only discover this at run time (the fatal EOF), stop and ask then.
- Host flags.
- Apple Silicon — add up front:
uname -m → arm64 → --container-architecture linux/amd64.
- Daemon-socket bind-mount failure (seen with colima) —
add only reactively, after the first run shows it:
--container-daemon-socket -.
Procedure
Confirm actionlint is clean on the staged workflows first —
via the actionlint skill, or a direct actionlint run.
act is the second line, not the first.
Propose the run to the operator in one line —
"run the staged workflow(s) through act to check they actually work?".
Declined → skip and let the commit proceed (act never blocks).
Check the preconditions above,
plus that act is installed (command -v act).
If act isn't installed, treat installing it as a separate ask —
it edits mise.toml (see below), which the run proposal doesn't authorize.
Any precondition missing → tell the operator, skip, let the commit proceed.
Run act on the staged workflow file(s) —
one -W run per staged workflow file.
act reads the working tree, not the index,
so first confirm the file has no unstaged edits
(git diff -- <file> is empty);
if it has, re-stage (git add) before running:
mise exec -- act -W .github/workflows/<staged-file> [host-flags]
mise exec -- only if act isn't already on PATH.
[host-flags] = whichever precondition-3 flags apply (arch / socket).
- act picks the event (
push, or the workflow's only one);
narrow with <event> or -j <job> if needed.
- Don't pre-filter jobs —
let unrunnable ones surface as env failures (step 5).
- Secrets: act reads
.secrets automatically when it exists
(or pass --secret-file); never invent them —
a missing secret is an environment skip, not a failure.
Read the result — split the two failure kinds,
because act's exit code doesn't. Go by the log markers:
- Success — jobs end
✅ / Job succeeded:
the workflow actually ran locally (within what act covers).
- Real workflow failure — a workflow step (not "Set up job")
exits non-zero on its own logic.
Show it, recommend a fix, but don't block the commit.
- Environment / unsupported failure — it couldn't get off the ground:
failed to start container, Error response from daemon,
unable to find image or no image for a macos/windows runner,
fatal ... EOF (image prompt), a missing secret, OIDC, socket, arch.
Also a command not found for a standard tool —
almost always the Medium image lacking it, not the workflow;
you can't inspect the image's contents, so default to env,
and ask the operator if it matters.
Report these as "not checked locally" with the reason, not as a failure.
- Can't tell? Ask the operator.
Never block the commit on act, whatever the outcome.
After the run (or a skip), the commit proceeds.
Installing act when it's missing
Install with mise — and note that Docker is a separate, required dependency;
act can't run without it.
Under [tools] in mise.toml:
[tools]
act = "<version>"
Resolve <version> from mise latest act
(all versions: mise ls-remote act) — pin it fully, no latest.
The raw name act resolves through aqua to aqua:nektos/act.
Never brew, go install, or a curl-pipe script.
If act can't be installed that way, or Docker isn't available,
tell the operator and skip the run.
Honest coverage — act is not your CI
A green act run is a smoke test, not proof the real CI passes.
act does not run macos/windows runners, OIDC,
or environments and env-scoped secrets;
it ignores concurrency, timeouts, continue-on-error,
step summaries, and annotations;
caching and services are partial.
Treat act as "does this get off the ground locally",
and the real CI as the source of truth.
Not in scope
- Replacing real CI (act is a smoke test — see coverage above).
- Supplying or inventing secrets / credentials.
- Forcing runs that can't work locally — report them, don't fight them.
Reporting
After the run, state which workflows or jobs ran,
the outcome of each
(passed / real failure / not-checked-locally with the reason),
and that the commit is not blocked either way.
1---2name: act3description: Second line of defence after actionlint: runs GitHub Actions workflows locally with act (nektos/act) to check they actually work, not just that they parse. Proposes the run on a workflow commit; never blocks it.4---56# act78act is the second line of defence for GitHub Actions workflows,9behind the actionlint skill.10actionlint is static — it proves a workflow parses11and its expressions and types are sound.12act is dynamic — it runs the workflow locally in Docker13to see whether it actually works.1415Use it as a proposal, not a gate:16once actionlint is clean on the staged workflows,17offer to run them through act and surface real failures —18but never block the commit on act.19Its local run can't cover everything,20and the local environment is fragile.2122## When in doubt, ask the operator2324If anything is ambiguous —25above all, whether a failure is the workflow's fault26or just the local environment's —27ask the operator instead of guessing.28One question is cheaper than calling a broken workflow fine,29or scaring the operator about a failure that's only a missing local image.30This applies to every step below.3132## When this runs3334- **After actionlint reports clean** on the staged workflow files —35 don't spend Docker on a workflow the cheap static check already rejected.36- It's a **proposal on every commit that touches a workflow**,37 not a requirement.38 Offer the run; if the operator declines, skip it — the commit is not blocked.3940## Preconditions4142The run needs a working Docker and a preconfigured image.43Check them first; if any is missing,44tell the operator, skip the run, and let the commit proceed —45never block over a precondition.46471. **Docker reachable** — `docker info` succeeds48 (any provider: Docker Desktop, colima, or podman via `podman info`).49 Missing binary or daemon down → act can't run; skip.502. **A default runner image is configured.**51 On its first run act interactively asks for an image size,52 and with no TTY it dies with `fatal EOF` — so it must be set up beforehand.53 The config lives in act's `actrc`54 (`~/.config/act/actrc`, `~/.actrc`,55 or `~/Library/Application Support/act/actrc` on macOS);56 a configured one has a `-P` default line.57 If none is set, ask the operator to run `act` once and pick **Medium**58 (~500MB; Large is ~70GB, Micro is node-only) — don't guess an image.59 If you only discover this at run time (the `fatal EOF`), stop and ask then.603. **Host flags.**61 - Apple Silicon — add up front:62 `uname -m` → `arm64` → `--container-architecture linux/amd64`.63 - Daemon-socket bind-mount failure (seen with colima) —64 add only reactively, after the first run shows it:65 `--container-daemon-socket -`.6667## Procedure68691. **Confirm actionlint is clean** on the staged workflows first —70 via the actionlint skill, or a direct `actionlint` run.71 act is the second line, not the first.722. **Propose the run** to the operator in one line —73 "run the staged workflow(s) through act to check they actually work?".74 Declined → skip and let the commit proceed (act never blocks).753. **Check the preconditions** above,76 plus that act is installed (`command -v act`).77 If act isn't installed, treat installing it as a separate ask —78 it edits `mise.toml` (see below), which the run proposal doesn't authorize.79 Any precondition missing → tell the operator, skip, let the commit proceed.804. **Run act on the staged workflow file(s)** —81 one `-W` run per staged workflow file.82 act reads the working tree, not the index,83 so first confirm the file has no unstaged edits84 (`git diff -- <file>` is empty);85 if it has, re-stage (`git add`) before running:8687 ```sh88 mise exec -- act -W .github/workflows/<staged-file> [host-flags]89 ```9091 - `mise exec -- ` only if act isn't already on `PATH`.92 - `[host-flags]` = whichever precondition-3 flags apply (arch / socket).93 - act picks the event (`push`, or the workflow's only one);94 narrow with `<event>` or `-j <job>` if needed.95 - Don't pre-filter jobs —96 let unrunnable ones surface as env failures (step 5).97 - Secrets: act reads `.secrets` automatically when it exists98 (or pass `--secret-file`); never invent them —99 a missing secret is an environment skip, not a failure.1005. **Read the result — split the two failure kinds,101 because act's exit code doesn't.** Go by the log markers:102 - **Success** — jobs end `✅` / `Job succeeded`:103 the workflow actually ran locally (within what act covers).104 - **Real workflow failure** — a *workflow step* (not "Set up job")105 exits non-zero on its own logic.106 Show it, recommend a fix, but don't block the commit.107 - **Environment / unsupported failure** — it couldn't get off the ground:108 `failed to start container`, `Error response from daemon`,109 `unable to find image` or no image for a macos/windows runner,110 `fatal ... EOF` (image prompt), a missing secret, OIDC, socket, arch.111 Also a `command not found` for a standard tool —112 almost always the Medium image lacking it, not the workflow;113 you can't inspect the image's contents, so default to env,114 and ask the operator if it matters.115 Report these as "not checked locally" with the reason, not as a failure.116 - **Can't tell?** Ask the operator.1176. **Never block the commit on act**, whatever the outcome.118 After the run (or a skip), the commit proceeds.119120## Installing act when it's missing121122Install with mise — and note that **Docker is a separate, required dependency**;123act can't run without it.124Under `[tools]` in `mise.toml`:125126```toml127[tools]128act = "<version>"129```130131Resolve `<version>` from `mise latest act`132(all versions: `mise ls-remote act`) — pin it fully, no `latest`.133The raw name `act` resolves through aqua to `aqua:nektos/act`.134Never `brew`, `go install`, or a curl-pipe script.135If act can't be installed that way, or Docker isn't available,136tell the operator and skip the run.137138## Honest coverage — act is not your CI139140A green act run is a smoke test, not proof the real CI passes.141act does **not** run macos/windows runners, OIDC,142or environments and env-scoped secrets;143it ignores concurrency, timeouts, continue-on-error,144step summaries, and annotations;145caching and services are partial.146Treat act as "does this get off the ground locally",147and the real CI as the source of truth.148149## Not in scope150151- Replacing real CI (act is a smoke test — see coverage above).152- Supplying or inventing secrets / credentials.153- Forcing runs that can't work locally — report them, don't fight them.154155## Reporting156157After the run, state which workflows or jobs ran,158the outcome of each159(passed / real failure / not-checked-locally with the reason),160and that the commit is not blocked either way.