# Workflow Loop Use this page for iterating on GitHub Actions workflows locally through Act. For role-level and spec-level iteration, see [Compose Loop](compose.md) and [Playwright Spec Loop](playwright.md). For the swarm deploy loop, see [Swarm Loop](swarm.md). ## 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](compose.md) or [Playwright Spec Loop](playwright.md); for the swarm deploy use the [Swarm Loop](swarm.md). ## 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=`). ### 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](../../../../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](../../../../utils/env/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.