Workflow Loop

Use this page for iterating on GitHub Actions workflows locally through Act. For role-level and spec-level iteration, see Compose Loop and Playwright Spec Loop. For the swarm deploy loop, see Swarm Loop.

When to use

  • Use this loop while developing, optimizing, or debugging a GitHub Actions workflow locally through Act.

  • For role-level or spec-level iteration use the Compose Loop or Playwright Spec Loop; for the swarm deploy use the Swarm Loop.

The loop

  • When you are developing, optimizing, or debugging GitHub Actions workflows, you SHOULD explicitly propose make act-workflow as the default iterative local debug loop.

  • You MUST NOT assume that Act should be used automatically for workflow work. If the user agrees with the proposal, you SHOULD use make act-workflow for the iteration loop.

  • After the user agrees to use Act, you SHOULD rerun make act-workflow after each focused workflow change and inspect the new output before making further edits.

  • If the workflow uses a distro matrix, you MUST iterate on one distro at a time instead of rerunning the whole matrix during the default debug loop.

  • Debian SHOULD be the preferred distro for that focused workflow iteration unless the failure is clearly distro-specific or the user asked for a different distro.

  • When you constrain an Act matrix run through ACT_MATRIX, you MUST use Act’s key:value syntax instead of key=value. Otherwise Act may ignore the filter and rerun the whole matrix.

  • For .github/workflows/call-test-workspace.yml, the preferred focused Debian example is make act-workflow ACT_WORKFLOW=.github/workflows/call-test-workspace.yml ACT_JOB=test-workspace ACT_MATRIX="dev_runtime_image:$(python3 -c 'from utils.distros import dev_runtime_image; print(dev_runtime_image("debian"))')".

  • You SHOULD avoid jumping straight to repeated remote CI reruns when make act-workflow can validate the workflow locally and the user agreed to use it.

Recovery & gotchas

act fails at “Set up job” on recent Docker

make act-* aborts with failed to copy content to container: mkdirat var/run... because the stock runner image’s /var/run symlink trips Docker 28/29’s stricter docker cp.

Fix: run make act-runner-image once, then prefix any act target with ACT_PLATFORM_IMAGE=local/act-runner-fixed:latest (e.g. ACT_PLATFORM_IMAGE=local/act-runner-fixed:latest make swarm-zombie app=<app>).

A green local workspace run does not clear CI

Two properties of the workspace suite differ between make act-workflow and a GitHub runner, so a class of defects is structurally invisible locally.

Nested Docker state does not survive a teardown locally, but does in CI. INFINITO_DOCKER_VOLUME is the named volume docker (default.env) and a host path (/mnt/docker) on GitHub. compose down -v removes a named volume and cannot remove a bind mount, so every workspace track starts on a clean inner daemon locally while CI carries the previous track’s containers into the next one. Defects that need a container, network, or bind source to outlive a teardown cannot reproduce under act.

The runtime identity differs. act sets ACT=true, so detect_gha_act() in runtime.py resolves INFINITO_RUNNING_ON_GITHUB=false where a real runner resolves true. Anything keyed off that fact behaves differently, and act cannot prove a fix to it either way. The image mirror is the exception: CI declares INFINITO_GHCR_MIRROR=true for the workspace suite, while a local run keeps the generated false and pulls upstream.

Run the local loop to iterate quickly; treat CI as the only authority for both classes.