At v1.0.0 the `github.com/imdario/mergo` module was relocated to `dario.cat/mergo`, so a plain version bump (as in https://gitea.com/gitea/runner/pulls/951) leaves the import path pointing at the old, unmaintained location. This PR updates the import in `act/container/docker_run.go` and adjusts `go.mod` accordingly. The public API (`mergo.Merge`, `mergo.WithOverride`, `mergo.WithAppendSlice`) is unchanged.
Supersedes https://gitea.com/gitea/runner/pulls/951.
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/954
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
Fixes#950.
After #819, the daemon flushes logs eagerly on the job-result entry (via the `stateNotify` path), so `Close()` typically runs `ReportLog(true)` with an empty buffer. Gitea's `UpdateLog` handler short-circuits on `len(Rows)==0` before honoring `NoMore`, so the final request never runs `TransferLogs` and `dbfs_data` rows leak. The server-side short-circuit is latent since the original Actions implementation in 2023; #819 made it deterministically reachable.
Workaround: inject a sentinel row in `Close()` after the daemon has exited so the final `UpdateLog` always carries at least one row. Done after the daemon waits so the sentinel can't be flushed before `ReportLog(true)` reads it.
https://github.com/go-gitea/gitea/pull/37631 drops the empty-rows short-circuit when `NoMore=true`; that would work with or without this PR.
Reviewed-on: https://gitea.com/gitea/runner/pulls/952
Reviewed-by: Nicolas <[email protected]>
Reviewed-by: Zettat123 <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
`NewGitCloneExecutor` holds a per-directory mutex while it `git checkout --force`s a remote action into the shared `<ActionCacheDir>/<UsesHash>`, but four read sites ran unlocked:
- `maybeCopyToActionDir`'s tar walk via `JobContainer.CopyDir`
- `prepareActionExecutor`'s `readAction` parse of `action.yml`
- `newReusableWorkflowExecutor`'s `model.NewWorkflowPlanner` after `cloneRemoteReusableWorkflow` released its lock
- `execAsDocker` when `ActionCache == nil`: `docker build` walks `contextDir` for the daemon-side build context
When two matrix jobs share a `uses:`, a read interleaved with a peer's checkout produces partial state — observed as `Cannot find module .../dist/index.js` and `setup-uv` failing on a half-written `action.yml`.
Exports `acquireCloneLock` as `AcquireCloneLock` and takes it at all four sites. `container.ImageExistsLocally` / `NewDockerBuildExecutor` and `model.NewWorkflowPlanner` are indirected through package-level vars so the docker-action build path and the reusable-workflow read site are testable without a real daemon, mirroring `ContainerNewContainer`. Three regression tests cover the higher-risk sites (`maybeCopyToActionDir`, `execAsDocker`, `newReusableWorkflowExecutor`); each fails if its `AcquireCloneLock` is removed.
Subsumed by https://gitea.com/gitea/runner/pulls/814 once that lands. Related: https://gitea.com/gitea/runner/pulls/930
---
This PR was written with the help of Claude Opus 4.7
---------
Co-authored-by: Nicolas <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/938
Reviewed-by: Nicolas <[email protected]>
Reviewed-by: Zettat123 <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
Aligns runner log output more closely with `actions/runner`:
- Strip the whale, rocket, cloud, construction, chequered-flag, and exclamation-mark glyphs from log lines and drop the now-unused `logPrefix` constant.
- Reword `no outputs used step '%s'` → `No outputs registered for step '%s'` (the original was ungrammatical and inaccurate — it fires when `set-output` references an unknown step ID).
- Wrap the docker pull/network/create/start phase of job container startup in a `::group::Starting job container` / `::endgroup::` collapsible section, mirroring `actions/runner`. Since act drives Docker through the SDK rather than the CLI, we can't echo `##[command]/usr/bin/docker create ...` lines verbatim — instead the helper emits a summary inside the group:
```
::group::Starting job container
image: <image>
name: <container-name>
network: <network-name>
::endgroup::
```
- Extracted the emit into a `printStartJobContainerGroup` helper (parallel to `printRunActionHeader` in `step_run.go`) and added a golden-style test `TestPrintStartJobContainerGroupGolden`.
- Drive-by: replace two remaining literal `"raw_output"` strings in `run_context.go` with the existing `rawOutputField` constant.
Closes#935
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/940
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
Audit-driven cleanup of `act/` test fixtures. Three commits:
**1. Remove dead fixtures** — 12 fixture directories that no Go test references: `dir with spaces`, `environment-variables`, `issue-104`, `issue-122`, `issue-141`, `localdockerimagetest_`, `node`, `parallel`, `python`, `uses-composite-with-inputs`, `uses-composite-with-pre-and-post-steps`, `shells/custom` (under `act/runner/testdata/`), plus `act/artifactcache/testdata/example`.
**2. Collapse `actions/node{12,16,20}` to a single `actions/node24` fixture** — the trio dispatched through identical `IsNode()` code paths and exercised the container's node binary, not the `using:` string. Bumps bundled deps to current (`@actions/core@^3`, `@actions/github@^9`, `@vercel/ncc@^0.38.4`) — both runtime packages are now ESM-only, so `index.js` is rewritten to ESM and `"type": "module"` added. Drops committed `node_modules/` and `package-lock.json` (now gitignored locally; `dist/` continues to be ignored by the repo-root `.gitignore` as before). Reduces `local-action-js/push.yml` to a single `test-node24` job and bumps four other stale `using: node12/16` references in fixtures.
**3. Bump test container base images** to `node:24-bookworm-slim` / `node:24-bookworm` / `ubuntu:24.04`. Replaces `node:16-buster-slim`, `node:16-buster`, `node:12.20.1-buster-slim`, and the EOL `node:12-buster-slim` / `node:16-buster-slim` / `ubuntu:18.04` base images in `actions/{docker-local,docker-local-noargs,action1}/Dockerfile`.
The runner's model still accepts `using: node12/16/20` for third-party actions in the wild — those constants are untouched.
Fixes: https://gitea.com/gitea/runner/issues/931
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/932
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
`cloneIfRequired` only ran the underlying clone executor when the target directory was missing, so a reusable workflow referenced by a moving ref (`uses: org/repo/.gitea/workflows/wf.yml@master`) was cached forever after the first invocation — edits to the source file never propagated.
Always invoke `git.NewGitCloneExecutor`. It handles existing repositories via fetch + pull + hard-reset, so branch and tag refs are brought up to date on each run, matching GitHub Actions semantics.
Drops the global `executorLock` too: `NewGitCloneExecutor` already takes a per-directory lock via `acquireCloneLock`, so the outer mutex only added unnecessary serialization across unrelated reusable-workflow clones — worse now that every invocation runs the full fetch.
Includes a regression test that drives the wrapper against a local bare repo, pushes a new commit on `master` between two invocations, and asserts the cached workflow file reflects the new tip.
Fixes: https://github.com/go-gitea/gitea/issues/37483
Fixes: https://gitea.com/gitea/runner/issues/726
Related: https://github.com/go-gitea/gitea/issues/30543
Would be subsumed by https://gitea.com/gitea/runner/pulls/814 ("WIP: Introduce new action cache") once that lands.
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/930
Reviewed-by: Zettat123 <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
Fixes#926.
Before:
<img src="/attachments/a5ae9221-eee2-410a-964e-6103ce126df4" alt="image.png" width="400">
After:
<img width="400" alt="image.png" src="attachments/2f2d67c4-6080-4ec3-9ae5-df33e6479920">
Also gets rid of a bunch of emojis in the logging and the obsolete link to `nektos/act` and align some other error messages.
---
This PR was written with the help of Claude Opus 4.7
---------
Co-authored-by: Nicolas <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/927
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
The `replace github.com/distribution/reference v0.6.0 => v0.5.0` was added defensively in [c4b57fbc](https://gitea.com/gitea/act_runner/commit/c4b57fbcb250b543848cdfedc89ebb6be20b50f4) (#775) against `github.com/docker/distribution v2.8.3`, whose `reference_deprecated.go` calls the now-removed `reference.SplitHostname`. However, nothing in act_runner's build graph imports `github.com/docker/distribution/reference`, so the file is never compiled and the replace has no effect.
Verified locally: removing the directive followed by `go mod tidy` keeps `distribution/reference` at v0.6.0, drops `docker/distribution` from the graph entirely, and `go build ./...` / `go vet ./...` both pass.
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/858
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
`s6-svscan` starts services in parallel, so `act_runner/run` could invoke `s6-svwait` before s6 had created the docker service's `supervise/` directory, failing with `s6-svwait: fatal: unable to s6_svstatus_read: No such file or directory`. Poll for the directory before waiting.
Fixes#760
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/851
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
Fixes#826.
Regressed in f2d54556 (#819, "perf: reduce runner-to-server connection load with adaptive reporting and polling"). That change added an early-return in `ReportState` whenever there was no state change and no pending outputs, so jobs that produce no log output and no step transitions for many minutes (e.g. a Linux kernel build) stop heartbeating. The server eventually marks the task as orphaned and cancels it while the runner is still executing.
The fix tracks the last successful `UpdateTask` time in an atomic and keeps the no-op skip only while the previous report is younger than `stateReportInterval`. The periodic state ticker fires at exactly `stateReportInterval`, so silent jobs now heartbeat each tick; redundant sends from a `stateNotify` firing right after a tick are still suppressed, preserving the perf intent of #819.
Test added: `TestReporter_StateHeartbeat` asserts the skip path within the interval and the heartbeat path after the interval elapses.
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/852
Reviewed-by: Nicolas <[email protected]>
Reviewed-by: Lunny Xiao <[email protected]>
Reviewed-by: ChristopherHX <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
- Fix `GXZ_PAGAGE` typo to `GXZ_PACKAGE`.
- Move `gxz`/`xgo` tool installs from `deps-backend` to `deps-tools`
and add `golangci-lint` there; `deps-backend` is just `go mod download`.
- Use `--color=always` + `printf` in `tidy-check` to match `fmt-check`.
- Use STATIC-gated `EXTLDFLAGS` instead of a uname-based toggle, and
move `-s -w` out of `EXTLDFLAGS` to match gitea's `-ldflags` layout.
- Pass `-s -w -linkmode external -extldflags "-static"` explicitly for
release-linux / release-windows; add `-s -w` to release-darwin.
Co-Authored-By: Claude (Opus 4.7) <[email protected]>
Merges the `gitea.com/gitea/act` fork into this repository as the `act/`
directory and consumes it as a local package. The `replace github.com/nektos/act
=> gitea.com/gitea/act` directive is removed; act's dependencies are merged
into the root `go.mod`.
- Imports rewritten: `github.com/nektos/act/pkg/...` → `gitea.com/gitea/act_runner/act/...`
(flattened — `pkg/` boundary dropped to match the layout forgejo-runner adopted).
- Dropped act's CLI (`cmd/`, `main.go`) and all upstream project files; kept
the library tree + `LICENSE`.
- Added `// Copyright <year> The Gitea Authors ...` / `// Copyright <year> nektos`
headers to 104 `.go` files.
- Pre-existing act lint violations annotated inline with
`//nolint:<linter> // pre-existing issue from nektos/act`.
`.golangci.yml` is unchanged vs `main`.
- Makefile test target: `-race -short` (matches forgejo-runner).
- Pre-existing integration test failures fixed: race in parallel executor
(atomic counters); TestSetupEnv / command_test / expression_test /
run_context_test updated to match gitea fork runtime; TestJobExecutor and
TestActionCache gated on `testing.Short()`.
Full `gitea/act` commit history is reachable via the second parent.
Co-Authored-By: Claude (Opus 4.7) <[email protected]>
Use `golangci-lint fmt` to format code, upgrading `.golangci.yml` to v2 and mirroring the linter configuration used by https://github.com/go-gitea/gitea. `gci` now handles import ordering into standard, project-local, blank, and default groups.
Mirrors https://github.com/go-gitea/gitea/pull/37194.
Changes:
- Upgrade `.golangci.yml` to v2 format with the same linter set as gitea (minus `prealloc`, `unparam`, `testifylint`, `nilnil` which produced too many pre-existing issues)
- Add path-based exclusions (`bodyclose`, `gosec` in tests; `gosec:G115`/`G117` globally)
- Run lint via `make lint-go` in CI instead of `golangci/golangci-lint-action`, matching the pattern used by other Gitea repos
- Apply safe auto-fixes (`modernize`, `perfsprint`, `usetesting`, etc.)
- Add explanations to existing `//nolint` directives
- Remove dead code (unused `newRemoteReusableWorkflow` and `networkName`), duplicate imports, and shadowed `max` builtins
- Replace deprecated `docker/distribution/reference` with `distribution/reference`
- Fix `Deprecated:` comment casing and simplify nil/len checks
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/act/pulls/163
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
## Summary
Adds a `container.bind_workdir` config option that exposes the nektos/act `BindWorkdir` setting. When enabled, workspaces are bind-mounted from the host filesystem instead of Docker volumes, which is required for DinD setups where jobs use `docker compose` with bind mounts (e.g. `.:/app`).
Each job gets an isolated workspace at `/workspace/<task_id>/<owner>/<repo>` to prevent concurrent jobs from the same repo interfering with each other. The task directory is cleaned up after job execution.
### Configuration
```yaml
container:
bind_workdir: true
```
When using this with DinD, also mount the workspace parent into the runner container and add it to `valid_volumes`:
```yaml
container:
valid_volumes:
- /workspace/**
```
*This PR was authored by Claude (AI assistant)*
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/810
Reviewed-by: ChristopherHX <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
## Summary
- Fix data race on `r.closed` between `RunDaemon()` and `Close()` by protecting it with the existing `stateMu` — `closed` is part of the reporter state. `RunDaemon()` reads it under `stateMu.RLock()`, `Close()` sets it inside the existing `stateMu.Lock()` block
- `ReportState` now has a parameter to not report results from runDaemon even if set, from now on `Close` reports the result
- `Close` waits for `RunDaemon()` to signal exit via a closed channel `daemon` before reporting the final logs and state with result, unless something really wrong happens it does not time out
- Add `TestReporter_EphemeralRunnerDeletion` which reproduces the exact scenario from #793: RunDaemon's `ReportState` racing with `Close`, causing the ephemeral runner to be deleted before final logs are sent
- Add `TestReporter_RunDaemonClose_Race` which exercises `RunDaemon()` and `Close()` concurrently to verify no data race on `r.closed` under `go test -race`
- Enable `-race` flag in `make test` so CI catches data races going forward
Based on #794, with fixes for the remaining unprotected `r.closed` reads that the race detector catches.
Fixes#793
---------
Co-authored-by: Christopher Homberger <[email protected]>
Co-authored-by: ChristopherHX <[email protected]>
Co-authored-by: rmawatson <[email protected]>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/796
Reviewed-by: ChristopherHX <[email protected]>
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
## Summary
- `actions/checkout`: v4/v5 → v6
- `actions/setup-go`: v5 → v6
- `docker/build-push-action`: v5 → v6
All other actions (`goreleaser/goreleaser-action@v6`, `docker/setup-qemu-action@v3`, `docker/setup-buildx-action@v3`, `docker/login-action@v3`, `crazy-max/ghaction-import-gpg@v6`) were already at their latest major versions.
No breaking changes affect the current workflow configurations. The main changes in the updated actions are Node.js 24 runtime upgrades and minor feature additions.
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/792
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
The worker goroutines in `NewParallelExecutor` had no panic recovery. A panic in any executor (e.g. expression evaluation) would crash the entire runner process, leaving all steps stuck in the UI because the final status was never reported back.
Add `defer`/`recover` in the worker goroutines to convert panics into errors that propagate through the normal error channel.
Issue is present in latest `nektos/act` as well.
Fixes https://gitea.com/gitea/act_runner/issues/371
(Partially generated by Claude)
Reviewed-on: https://gitea.com/gitea/act/pulls/153
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
## Summary
- Bumps Go version from 1.24.0 (toolchain go1.24.11) to 1.26.0
- Fixes CI `govulncheck` failures caused by three standard library vulnerabilities in go1.24.11:
- GO-2026-4341: Memory exhaustion in `net/url` query parameter parsing
- GO-2026-4340: Handshake messages at incorrect encryption level in `crypto/tls`
- GO-2026-4337: Unexpected session resumption in `crypto/tls`
## Test plan
- [x] `make vet` passes
- [x] `make build` passes
- [x] `make test` passes (includes `govulncheck` and all unit tests)
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/787
Reviewed-by: ChristopherHX <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>