Gitea carries a copy of this evaluation layer, as
modules/actions/jobparser/evaluator.go says itself: "copied from
runner.expressionEvaluator, to avoid unnecessary dependencies". It now
lives in gitea.dev/actionslib/pkg/expreval, so both sides split
sub-expressions, interpolate, keep the type of a lone expression and
apply the default status check the same way.
expressionEvaluator keeps the logging and the mask of every single
evaluation, which is what the shared Evaluator takes a function for.
Signed-off-by: Lunny Xiao <[email protected]>
Gitea needs the workflow model and the expression evaluator to parse
workflows and to build the task payload this runner consumes, so it had
to depend on gitea.com/gitea/runner for it. Both packages now live in
gitea.dev/actionslib, the module both sides already share, and this
repository consumes them from there.
The GithubContext helpers that need a git checkout on disk (SetRef,
SetSha and SetRepositoryAndOwner) are not moved: they are runner only and
would drag a git client and the act context logger into the shared
module. They become functions of the new act/ghcontext package, together
with their tests.
act/common.CartesianProduct moved to the shared model package as well,
act/model was its only user. act/model/testdata/container-volumes is now
act/runner/testdata/container-volumes, its only user is runner_test.go.
The x-runner-uuid and x-runner-token header names come from
actionslib too, so the runner and Gitea cannot drift apart.
The generated runner API code moved along with the repository, from
gitea.dev/actions-proto-go to gitea.dev/actionslib, so the imports of
ping/v1 and runner/v1 follow the new module path. Both cannot be used at
once: the generated types would no longer be the same types.
Signed-off-by: Lunny Xiao <[email protected]>
Revert #1136 and use actionslib instead.
revert chore: bump the module path to `/v3`, take the version from the VCS stamp (#1136)
gitea can not consume the runner's api by version while it's version mismatches the module version:
```
go: gitea.com/gitea/[email protected]: invalid version: module contains a go.mod file,
so module path must match major version ("gitea.com/gitea/runner/v3")
```
Fix that by bumping the module version now. The existing `v3.0.0` and `v3.0.1` tags stay unusable, so a new tag is needed after this lands.
Also drop the version `-X` linker flags, which would otherwise have to repeat the new path in both `Makefile` and `.goreleaser.yaml`, where a stale path makes injection silently no-op. Go has recorded the module version in the build info since 1.24, so `Version()` reads it from there, keeping the variable as an override for builds without a VCS stamp.
That part started as https://gitea.com/gitea/runner/pulls/1137 but belongs here: the stamp resolves against the tags that are legal for the module path, so without the `/v3` bump it would report `v1.0.9-0.<ts>-<sha>`. Since `release-nightly.yml` triggers on every push to `main`, splitting them would publish a nightly with a `v1` version.
---------
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1136
Reviewed-by: techknowlogick <[email protected]>
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1148
Reviewed-by: Zettat123 <[email protected]>
gitea can not consume the runner's api by version while it's version mismatches the module version:
```
go: gitea.com/gitea/[email protected]: invalid version: module contains a go.mod file,
so module path must match major version ("gitea.com/gitea/runner/v3")
```
Fix that by bumping the module version now. The existing `v3.0.0` and `v3.0.1` tags stay unusable, so a new tag is needed after this lands.
Also drop the version `-X` linker flags, which would otherwise have to repeat the new path in both `Makefile` and `.goreleaser.yaml`, where a stale path makes injection silently no-op. Go has recorded the module version in the build info since 1.24, so `Version()` reads it from there, keeping the variable as an override for builds without a VCS stamp.
That part started as https://gitea.com/gitea/runner/pulls/1137 but belongs here: the stamp resolves against the tags that are legal for the module path, so without the `/v3` bump it would report `v1.0.9-0.<ts>-<sha>`. Since `release-nightly.yml` triggers on every push to `main`, splitting them would publish a nightly with a `v1` version.
---------
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1136
Reviewed-by: techknowlogick <[email protected]>
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
Every `${{ }}` part was spliced as raw text into a synthesized `format('...', <raw>)` call and re-parsed, so unbalanced parentheses restructured the whole expression:
```yaml
run: echo ${{ 1) && (2 }} # panicked with "did not evaluate to a string"
if: ${{ 1 }} ${{ 0) && (0 }} # silently skipped the step
```
One scanner shaped like GitHub's template reader now splits every value, and each part is evaluated on its own, so nothing builds an expression out of text.
An empty input to `Evaluate` asked `success()` whatever the caller requested, so a post step running under `always()` asked the wrong question.
Same fix as https://github.com/go-gitea/gitea/pull/38754 on the Gitea side.
Written by Claude Opus 5.
---------
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1146
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
`config.example.yaml` now has every value commented out, as gitea's `app.example.ini` does, so it documents each option with its default instead of imposing it. Copying it no longer pins values the runner would otherwise pick, and a changed default reaches configs that never named the option.
`config init` writes the file to configure: a header comment and no option, so every option keeps its default. It refuses to overwrite an existing config without `--force`, and writes `config.yaml` in the working directory when `-c` is absent.
`config set`, `add` and `remove` keep such a file readable. Comments go back to the indentation they were written at, which the encoder drops for a comment block that has no key below it, and a file of only comments keeps its text instead of being emptied by the first edit.
Also rewrote the config docs sections for clarity.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1145
Reviewed-by: Lunny Xiao <[email protected]>
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
Service containers were started and then left alone, so a job's first step could run while a database was still starting up. The runner now waits for every service whose image or `options` declare a healthcheck, as GitHub does. An unhealthy service fails the job with its container log, one that never becomes healthy fails it after `container.service_ready_timeout` (default `5m`, negative disables the wait), and one that exits without a healthcheck only gets its log and a warning.
The started containers also fill the `job` context, whose fields existed but were never populated: `job.container.{id,network}` and `job.services.<id>.{id,network,ports}`. `ports` is keyed by the plain container port, so `job.services.postgres.ports['5432']` resolves to the host port Docker picked.
---------
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1107
Reviewed-by: silverwind <[email protected]>
Co-authored-by: bircni <[email protected]>
Changing a setting after `generate-config` meant hand-editing YAML, which is awkward in provisioning scripts. `config` now edits an existing file in place:
```bash
./gitea-runner -c config.yaml config set runner.capacity 4
./gitea-runner -c config.yaml config set runner.envs.MY_VAR value
./gitea-runner -c config.yaml config add runner.labels 'ubuntu:docker://node:22'
./gitea-runner -c config.yaml config remove runner.labels 'ubuntu:docker://node:22'
./gitea-runner -c config.yaml config get runner.labels
```
Edits go through the YAML node tree, so comments, key order and the blank lines between top-level sections survive — a test asserts that appending a label to `config.example.yaml` changes nothing but the added line. Keys are resolved by reflecting over the `Config` struct's yaml tags, so an unknown key or a value of the wrong type is rejected before the file is touched. The write is atomic and keeps the file's symlink, owner, mode and line endings.
---------
Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1140
Reviewed-by: silverwind <[email protected]>
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: bircni <[email protected]>
`::set-env::` and `::add-path::` let a step rewrite the environment of every later step from its own output, which the runner honoured silently. They are now refused, as GitHub has done since 2020, and `ACTIONS_ALLOW_UNSECURE_COMMANDS` opts back in per step or job. Support for that variable is new here too, and is the only opt-in, matching GitHub rather than adding a runner config key on top.
Annotations keep their source location: Gitea has no annotation store and its web UI strips command properties, so `::error file=main.go,line=12::msg` is rendered as `::error::main.go:12: msg`.
`DEVELOPMENT.md` writes down the log line encoding rules this relies on.
---------
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1109
Reviewed-by: silverwind <[email protected]>
Co-authored-by: bircni <[email protected]>
`ACTIONS_RESULTS_URL` names one origin serving every `github.actions.results.api.v1` service. Gitea serves the artifact half and this runner the cache half, so announcing `ACTIONS_CACHE_SERVICE_V2` while that URL pointed at Gitea was a promise the environment could not keep, and `docker buildx` posted its cache calls at Gitea and got a 404.
The cache server now forwards the artifact half to the instance each job registers with, so it is the whole results service and jobs are pointed at it. The announcement follows, and the bundle patch follows the cache URL instead.
Also fixes three things no JavaScript client reached: camelCase in the v2 responses where the Go clients read proto names, the missing `x-ms-request-id` on blob uploads that panics buildkit, and `cache.external_server` passed through without the trailing slash the v1 client concatenates onto.
Tests run the real actions against the services they look for: `actions/cache` over both API versions, the artifact actions up and back down through the forwarding, and `setup-node`. The regression itself is covered by asserting that whatever a job is handed as `ACTIONS_RESULTS_URL` answers a cache service call.
Fixes https://gitea.com/gitea/runner/issues/1139
Reviewed-on: https://gitea.com/gitea/runner/pulls/1141
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
`coerceToString` covered only a few kinds and returned the unconverted `reflect.Value` for the rest, so callers rendered contexts as `<*model.GithubContext Value>` and mangled sized integers and floats the same way. It now returns a string, which makes that placeholder unrepresentable, and is exported as `CoerceToString` so Gitea can drop its own diverging copy. It also takes an already reflected value, so internal callers need no conversion and no guard against the zero `Value`.
`format()` panicked whenever a lone `}` was followed by anything other than another `}`. Only a trailing `}` reached the existing unmatched-brace check, so an expression such as `format('a}b')`, which any workflow can write, took the process down instead. It now returns that same error.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1135
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Carries over the applicable rules from https://gitea.com/gitea/gitea `AGENTS.md`, including the Conventional Commits and `make lint-go-windows` requirements this repo already enforces but never documented for agents. Swaps `Co-Authored-By` for the `Assisted-by` trailer.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1134
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
1. Drop `Masterminds/semver`, the Docker API version check needs dotted-numeric comparison and moby's client ships `versions.GreaterThanOrEqualTo`.
1. A malformed API version now reports as unsupported instead of panicking.
1. Drop `pkg/errors`, nothing used stack traces, `Wrap` or `Cause`.
1. Drop its depguard rule, and the `io/ioutil` one that only masked staticcheck's `SA1019`.
1. Re-sync `act/container/docker_cli.go` and `act/container/docker_cli_test.go`, copies of docker/cli's `opts.go` and `opts_test.go`, from a March and a 2022 commit to the version go.mod pins.
1. Record the local deviations in each header.
1. Fix `invalidParameter`, which lacked `Unwrap`, hiding the wrapped cause from `errors.Is` and `errors.As`.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1132
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
Docker 29.7 extracts copied archives through `os.Root`, which refuses to follow a symlink to an absolute target, so copying into `/var/run/act` fails with `path escapes from parent` on the many images that link `/var/run` to `/run`. The daemon now resolves every path component for us before any tar entry name is built, and the destination is created with one directory entry per missing component, which no daemon version rejects.
Verified against real daemons (29.4.0, 29.5.0, 29.5.1, 29.5.3, 29.6.2, 29.7.0-rc.1) with `debian:bookworm` (absolute symlink) and `alpine:3` (relative symlink), and against `moby/go-archive` v0.2.0 through the pending fix branch.
1. Fixes https://gitea.com/gitea/runner/issues/1128
1. Upstream bug: https://github.com/moby/moby/issues/53258
1. Supersedes the no-op change in https://gitea.com/gitea/runner/pulls/1129, which cannot help since the daemon strips leading slashes itself
Reviewed-on: https://gitea.com/gitea/runner/pulls/1130
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
`CopyTarStream` creates the destination directory by extracting a one-entry tarball with `DestinationPath: "/"`, but named that entry with the absolute `destPath`. Docker Engine 29.5+ tightened path validation on the copy API and rejects absolute entry names against a `/` destination with `statat var/run/act/actions/<sha>: path escapes from parent`, so the action directory never reached the job container and `actions/checkout` failed during "Set up job". Stripping the leading slash makes the entry relative, matching what the sibling `copyDir` already does and the upstream fix in nektos/act v0.2.89. Adds a regression test asserting the mkdir tarball entry is relative.
Fixes#1128
Reviewed-on: https://gitea.com/gitea/runner/pulls/1129
Reviewed-by: Zettat123 <[email protected]>
Workflow-controlled `jobs.<job>.container.options` were merged directly into the
Docker `HostConfig`. When the runner's privileged mode is disabled, only
`Privileged` was forced to `false` — host namespace flags, capability expansion,
security-profile overrides, and device/runtime access from the workflow YAML
survived into the final `HostConfig`. A workflow author could therefore enter
host PID/IPC namespaces and execute commands as root on the runner host:
```yaml
container:
image: ubuntu:22.04
options: >-
--pid=host --ipc=host --cap-add=ALL
--security-opt seccomp=unconfined --security-opt apparmor=unconfined
```
## Fix
`mergeContainerConfigs()` now strips the dangerous options-derived `HostConfig`
fields before merging when privileged mode is off: `PidMode`, `IpcMode`,
`UTSMode`, `CgroupnsMode`, `UsernsMode`, `CapAdd`, `SecurityOpt`, `Devices`,
`DeviceCgroupRules`, `DeviceRequests`, `VolumesFrom`, `Runtime`, `CgroupParent`,
and `Sysctls`. Each strip emits a warning, matching the existing
`--network ignored` handling. Options remain honored when privileged mode is
enabled, since the administrator has already opted into host access.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1058
Reviewed-by: Zettat123 <[email protected]>
Only the verbatim value of a secret was masked, so a secret leaked through an action that serialized it stayed readable: `toJSON(secrets)` escapes it, an Authorization header carries it base64-encoded, a URL percent-encodes it. Each secret and `::add-mask::` value is now masked in those forms too, matching the value encoders of GitHub's runner. Encodings that leave the value unchanged are skipped, so a plain token still costs a single replacement pair. Includes regression tests.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1108
Reviewed-by: Zettat123 <[email protected]>
Starting two runner daemons with the same `.runner` file makes both present an
identical UUID+token, so Gitea treats them as one runner and they cancel each
other's jobs. This adds a non-blocking advisory lock on a sibling
`<runner-file>.lock`: the daemon (and `register`) acquire it at startup, and a
second process on the same host fails fast with a clear error instead of silently
interfering. The OS releases the lock when the process exits — including a hard
kill — so no stale lock is left behind. Legitimate multi-runner setups are
unaffected since each already uses its own `runner.file`.
Note: this covers the common single-host case; two hosts sharing a copied
`.runner` (e.g. over NFS) would still need server-side detection in Gitea.
---------
Co-authored-by: Zettat123 <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1099
Reviewed-by: Zettat123 <[email protected]>
Serves `github.actions.results.api.v1.CacheService` next to the v1 cache API, sharing its store, plus the subset of the Azure blob protocol the toolkit uploads with. On by default via `cache.v2`, and works with `external_server`.
Clients reach it through two edits in the action's own bundle: the GHES check is opened, and the cache service URL is taken from `ACTIONS_CACHE_URL`.
The same GHES check is what makes the stock `actions/upload-artifact` and `download-artifact` abort on Gitea. Opening it makes them work without the `gitea-upload-artifact` fork, from `[email protected]` on.
Verified against 118 real bundles, every major version of 16 actions: 92 patched, the rest deliberately left alone, and every patched bundle checked with `node --check`. Also end to end against pinned `actions/[email protected]` with an unreachable results URL, so only the patch can make the cache work.
---------
Co-authored-by: silverwind <[email protected]>
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1110
Reviewed-by: silverwind <[email protected]>
Leaked per-job networks each hold a subnet of the daemon's address pool and nothing reclaimed them, so a host eventually fails every job with `all predefined address pools have been fully subnetted`. This is what CI hit in https://gitea.com/gitea/runner/actions/runs/742177.
- teardown no longer loses a container, and its network with it: a failed `ContainerRemove` was reported as success, a container whose id was never learned was skipped, and the daemon's own `AutoRemove` teardown was raced
- the idle cleanup reclaims what teardown cannot: networks carry `com.gitea.runner.uuid`, so a runner only touches its own, and a cutoff keeps a job starting during the pass out of scope
- pull failures reported mid-stream were discarded, surfacing later as a confusing `No such image`; they now propagate, and fall back to a local copy instead of failing the job
- `NetworkCreate` retries pool exhaustion briefly, then says which knobs to turn
- digest-pinned images are not re-pulled, and removal kills first so it never waits out Podman's stop timeout (measured 10.1s → 0.09s per container)
---------
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1124
Reviewed-by: bircni <[email protected]>
Set `http_proxy`, `https_proxy` and `no_proxy` in the runner's environment and everything the runner controls uses them.
Go already read them for the runner's own requests. This adds jobs, in lower and upper case, service containers, and Dockerfile action builds.
Some hosts are added to `no_proxy` for jobs so they stay direct: the cache server, loopback, the job's service containers, and a `tcp://` Docker daemon. Without the last one the Docker client sends its API calls to the proxy and docker-in-docker breaks. Gitea is not added.
Images are pulled by the Docker daemon, which has its own proxy setting. In the `dind` images it reads these same variables. The runner warns at startup if it has a proxy and the daemon does not.
Fixes https://gitea.com/gitea/runner/issues/1118, originally reported as https://gitea.com/gitea/runner/issues/708.
---------
Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1112
Reviewed-by: silverwind <[email protected]>
Co-authored-by: bircni <[email protected]>
An unchecked type assertion panics on input it did not expect, as a missing `tool_cache` key did in https://gitea.com/gitea/runner/pulls/1122.
Every flagged site is now handled where it can fail, or typed so it cannot: a `lock.Keyed` replaces the two `sync.Map` mutex registries, and the reporter's outputs carry an explicit sent flag. Mocks keep their assertions, a mismatch there is a setup error the panic names.
Bugs it turned up (only the first is reachable from workflows):
1. A scalar `matrix.include` or `matrix.exclude`, e.g. `include: foo` or `include: [1, 2]`, panicked the runner with `interface conversion: interface {} is string, not map[string]interface {}`. Verified against `main`, it is now a workflow error. `OnSchedule` panicked the same way on a malformed `on.schedule` entry.
1. `ExternalURL()` panicked on the nil listener after `Close()`, the port is now resolved once at startup.
1. `errors.Is(err, git.ErrShortRef)` followed by `err.(*git.Error)` panics as soon as anything wraps that error, so it is `errors.As` now.
1. An output name the server acknowledged without ever being sent one was recorded as sent forever, which silently dropped a later value for that name.
https://gitea.com/gitea/runner/commit/48576ab3e5c485af5a21ee6d21888a049ecef42b fixes one discovered issue: a matrix key holding a nested object was logged and then run as if the job had no matrix, so it now fails like an unknown `exclude` key.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1123
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
1. Docker rejects two mounts on one target, so a `container.volumes:` or `--volume` aimed at `/opt/hostedtoolcache` failed the job with `Duplicate mount point`. Job and service volumes now displace the mount on the same path, and `name:/target:ro` no longer mounts read-write at the literal path `/target:ro`.
1. Setting `RUNNER_TOOL_CACHE` only changed what the variable said, the cache stayed where it was, so tools writing to it landed outside the mount and `${{ runner.tool_cache }}` disagreed with the variable. It now relocates the cache. Leaving it unset behaves as before.
1. Unknown `config.yaml` keys now warn instead of being dropped without a trace.
Fixes https://gitea.com/gitea/runner/issues/813
---------
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1122
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
Adds `runner.hooks.job_started` and `runner.hooks.job_completed`: operator scripts that run inside the job environment, before the job's first step and after its last one.
```yaml
runner:
hooks:
job_started: /hooks/started.sh
job_completed: /hooks/completed.sh
```
Equivalent to GitHub's `ACTIONS_RUNNER_HOOK_JOB_STARTED` / `ACTIONS_RUNNER_HOOK_JOB_COMPLETED`, which are read when unset: output is scanned for workflow commands, `$GITHUB_ENV` and `$GITHUB_PATH` are read back, and a non-zero exit fails the job.
Fixes: https://gitea.com/gitea/runner/issues/779
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1111
Reviewed-by: silverwind <[email protected]>
Full suite 178s → 96s, and a run's log 12MB → 2KB. `act/runner` was 177s of the 178s, serialised behind one docker daemon.
- Its fixtures now run in parallel, bounded by a slot count instead of `go test -parallel`, with a per-test container name prefix and a pinned `MaxParallel`.
- Fixtures asserting a job failure leaked their container and network (`AutoRemove` was at the act-CLI default), filling the daemon's address pool over time.
- Replaced sleeps used as synchronisation in the parallel-executor and cache-handler tests.
- Dropped duplicate coverage: two files re-testing `NewParallelExecutor`, a test asserting on its own semaphore, and `TestDockerActionForcePullForceRebuild`, whose config `runTest` discarded.
- `fmt-check`/`security-check` move from `make test` to a `checks` target; `security-check` no longer installs `xgo` and `gxz`.
- Test flags follow gitea: `GOTEST_FLAGS ?= -race -timeout 20m -parallel 8`, with `-cover`/`-coverprofile` left in the target. Dropped `-v`, since a failing package still prints its full output without it.
Coverage unchanged at 73.4%.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1121
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
A docker action can declare `runs.pre-entrypoint` and `runs.post-entrypoint` next to `runs.entrypoint` — the docker equivalent of a javascript action's `runs.pre`/`runs.post`. Neither key existed on `ActionRuns`, so the YAML decoder dropped them and docker actions silently skipped their setup and cleanup stages.
Only the entrypoint is stage specific. [`ContainerActionHandler`](https://github.com/actions/runner/blob/main/src/Runner.Worker/Handlers/ContainerActionHandler.cs) selects `Data.Pre`/`Data.Post` per stage but evaluates `runs.args` and `runs.env` unconditionally, so `docker create` receives the action's args and env on every stage. The `entrypoint` input stays main only, because it is read inside the main branch.
The stage is carried as the existing `stepStage` value rather than a separate parameter, and the pre and post stages reuse the action path resolution that `runPreStep`/`runPostStep` already open-coded three times, so the diff also drops those copies.
Known gap: `stepActionLocal.pre()` is a no-op for every `using`, so `pre-entrypoint` does not run for `uses: ./local-action` while `post-entrypoint` does. Closing it requires reading the action model before the main stage and adding a pre case to `getIfExpression` for `pre-if`, which also changes local node, go and composite actions — better as its own change.
---------
Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1106
Reviewed-by: silverwind <[email protected]>
Co-authored-by: bircni <[email protected]>
Passes the `runner` context values act already knew but never reported to jobs:
1. `runner.name` / `RUNNER_NAME` — registered runner name, hostname for `exec`
2. `runner.environment` / `RUNNER_ENVIRONMENT` — `self-hosted`
3. `RUNNER_WORKSPACE` — parent of `GITHUB_WORKSPACE`
4. `runner.debug` / `RUNNER_DEBUG` — `1` when `ACTIONS_STEP_DEBUG` is set
`ImageOS` now prefers the release named in the resolved image tag, so `ubuntu-latest` mapped to `runner-images:ubuntu-24.04` reports `ubuntu24` instead of the hardcoded `ubuntu20`. The `runs-on` label stays the fallback.
---------
Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1105
Reviewed-by: silverwind <[email protected]>
Co-authored-by: bircni <[email protected]>
Command data is percent-escaped on the wire because Gitea's job log cannot carry a newline, and the renderer decodes it.
1. Unescaping iterated a map, so order was random per process: `%250A` decoded to a newline instead of a literal `%0A` about two runs in three. Now a precompiled `strings.NewReplacer`.
1. Escape the data of command lines the runner writes itself (`##[error]`, `::group::Run …`), so decoding returns the original text instead of mangling a `%`. Multi-line runner errors also get real line breaks instead of a literal `\n`.
1. Register secrets in escaped form too — one containing `%` reaches the log as `%25…` and was never masked.
1. Decode in `jobLogFormatter`, so `gitea-runner exec` matches the web view.
## Relation to the Gitea PR
https://github.com/go-gitea/gitea/pull/38659 makes the renderer decode command data. Point 2 is required by it; the rest stand alone. Either order works — until both land, a new runner on old Gitea shows `%25`, an old runner on new Gitea shows the mangling point 2 fixes.
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1120
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
Mirrors every release artifact into Cloudflare R2 alongside the existing AWS S3
upload, so both buckets carry the same objects during a parallel period. S3 is
untouched and stays authoritative for now; once R2 is confirmed complete it can
be dropped by deleting the `blobs:` block and pointing this at R2 alone.
### Why `publishers:` and not a second `blobs:` entry
goreleaser's blob pipe authenticates from the global `AWS_*` environment and has
no per-entry credential fields, so two different credential sets (AWS S3 and
Cloudflare R2) cannot coexist in `blobs:`. Custom publishers do support
per-entry `env:`, which is the supported way to isolate the two.
### Why `curl --aws-sigv4` and not `aws`/`rclone`
The CI image `docker.gitea.com/runner-images:ubuntu-latest` ships none of `aws`,
`rclone`, `s3cmd` or `mc`, but does ship curl 8.5 with `--aws-sigv4`. This keeps
the change zero-install. Credentials are fed to curl through a config file on
stdin rather than argv, so they never appear in the process list.
### Behaviour
- Object layout is identical to S3 (`gitea-runner/<version>/<artifact>`), so
migrating consumers later means changing only the host, not the path.
- Nightly gets R2 too, matching the existing nightly-to-S3 behaviour.
- Missing R2 configuration fails the build. Because custom publishers run as the
very last step of the publish pipeline, a preflight `--check-config` step runs
right after checkout so the failure happens before anything is built or
published rather than after the release already exists.
- Verified against a local MinIO instance (site region `auto`, matching R2):
successful upload byte-compared after download, plus the missing-variable,
wrong-credentials, missing-file and bad-argument paths.
### Required repository secrets
These must be configured before the next release run, otherwise the new
preflight step will fail the workflow:
- `R2_ENDPOINT` (full base URL, e.g. `https://<account>.r2.cloudflarestorage.com`)
- `R2_BUCKET`
- `R2_ACCESS_KEY_ID`
- `R2_SECRET_ACCESS_KEY`
### Known, deliberate wart
The publisher runs 109 times for 73 distinct object keys: goreleaser's release
pipe already registers `release.extra_files` as `UploadableFile` artifacts, and
`internal/exec` appends the publisher's own `extra_files` on top with no
de-duplication. It cannot be globbed away because `gobwas/glob` has no
substring-exclusion matcher, so `./**.sha256` cannot be narrowed to exclude
`*.xz.sha256`. It is harmless since PUT is idempotent, and the redundant
`./**.xz` glob is kept on purpose so the publisher declares its own complete
file set instead of implicitly depending on the `release:` block's globs. This
is documented in a comment above the block.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1114
Reviewed-by: Zettat123 <[email protected]>
Fixes https://gitea.com/gitea/runner/issues/667
Fixes https://gitea.com/gitea/runner/pulls/1103
`container.options` is parsed with docker/cli's `addFlags`, which omits the flags docker/cli registers on the `create` and `run` commands themselves. Those were rejected as unknown — most visibly `--platform`.
| Flag | Behavior | Why |
| --- | --- | --- |
| `--platform` | overrides the runner-wide container architecture | the only way to pick an image architecture per job, e.g. across a matrix |
| `--pull always\|missing\|never` | `always` forces a pull, `never` skips it | `force_pull` is runner-wide config today, with no per-job control |
Both are resolved in `NewContainer` because the image pull runs before the container is created and the two have to agree.
`--name`, `--quiet` and `--disable-content-trust` are now accepted and ignored, and `--use-api-socket` is rejected pointing at `container.docker_host`, so a valid `docker create` line no longer fails outright.
<sub>Written by Claude (Opus 4.8).</sub>
---------
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1104
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
Fixes https://gitea.com/gitea/runner/issues/659
Docker teardown was chained with `Then`, which stops on the previous step's error and on a cancelled context, so the first failure orphaned everything downstream. A volume that is still in use, or a flaky daemon, left the service containers and the job network behind — matching the reports in the issue, where the leaks show up after failed or overloaded jobs.
Cleanup is now straight-line and best-effort: every step runs regardless of what failed before it, and the errors are joined. Two things follow from that:
- Service containers are removed before the job volumes, since a service can hold one via `--volumes-from` in its options. The network stays last, once every container has detached.
- Only job container and volume errors are returned. Service and network errors are logged, as before, because `stopJobContainer()` also runs as the pre-flight step of job start, where a network cleanup error must not abort the job.
The nil check on `rc.JobContainer` is kept, but it is not the leak reporters are hitting: `container.NewContainer` never returns nil in the docker build, so cleanup with no job container is only reachable in the `WITHOUT_DOCKER` stub.
Addresses https://gitea.com/gitea/runner/pulls/1066#issuecomment-1239073.
Not covered here: the `buildx_buildkit_*` volumes reported in the issue are created by buildx inside the job, not by the runner, so no runner-side teardown removes them.
---------
Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1066
Reviewed-by: silverwind <[email protected]>
This PR adds a new `cache.external_secret_file` config, which points at a file holding the secret. So the secret can come from a mounted Kubernetes/Docker secret while the rest of the config stays plain text.
```yaml
cache:
external_server: "http://cache-host:8088/"
external_secret_file: /path/to/cache_external_secret
```
---------
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1100
Reviewed-by: bircni <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
`Statfs_t.Bavail` is unsigned on Linux but signed on FreeBSD, so `Bavail * uint64(Bsize)` in `internal/app/run/disk_unix.go` fails to compile for the freebsd targets goreleaser cross-builds, breaking the nightly release (introduced in https://gitea.com/gitea/runner/pulls/1090). The `checks` workflow only builds for the host, so it never cross-compiles freebsd and stayed green.
Casting both operands to `uint64` makes the arithmetic signedness-agnostic across all unix variants.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1098
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
Opt-in local task-admission checks under a `health_check` config section (disabled by default):
- pause new task fetching when free disk space on the workspace volume is below the configured minimum
- optional executable health-check script — a non-zero exit, timeout, or start failure marks the runner unavailable
- checks run only while the runner is idle; the last result is reused while a job is active, and polling resumes automatically on recovery
- `/readyz` reports task-admission readiness (reusing the poll loop's last check); `/healthz` stays a process-liveness endpoint
---------
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1090
Reviewed-by: silverwind <[email protected]>
## Problem
On host-mode Windows runners, a job can leave processes running after it finishes,
and those leftovers hold file handles that block deletion of the workspace.
Today a step's tree is only torn down when the step is *cancelled*
(`process.Killer`). A step that completes leaves whatever it spawned alive, and
the existing workspace scan in `terminateRunningProcesses` misses two shapes of
leftover: orphans whose parent already exited (no tree to walk, and their
executable often lives outside the workspace), and processes that merely *run in*
the workspace but reference no path from it — `Win32_Process` exposes no working
directory, so the scan cannot match them.
## Solution
Two additions in `internal/pkg/process`, both no-ops outside Windows:
- **`process.Group`** — a job-scoped Windows Job Object created with
`JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`. Step processes are assigned to it before
they get their own `Killer`, so the step's job nests inside the job's:
cancellation still kills exactly the step's tree, while `Remove` closing the
group makes the kernel terminate everything still assigned, whatever its
parentage. The kernel also drops the handle when the runner exits, so a crashed
runner cannot strand processes.
- **`process.KillProcessesWithCWDUnder`** — a best-effort net for processes that
never joined the job (started via a service or scheduled task). It reads each
process's working directory from its PEB and terminates those under a workspace
dir. Processes it cannot open are skipped.
---------
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1080
Reviewed-by: silverwind <[email protected]>
Reshapes the job log's "Set up job" section to mirror `actions/runner`:
- runner name/version, then `Runner Information` (labels, task, job, repository, event) and `Operating System` groups
- every required action downloaded up front under `Prepare all required actions`, each as `Download action repository '<action>@<ref>' (SHA:<sha>)`
- `Complete job name` closes the section
Downloading up front is the one behavioral change: the same set was already fetched during the pre stage regardless of a step's `if`, now just before the first pre step, so a download failure is reported against the job rather than a step.
---------
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1089
Reviewed-by: silverwind <[email protected]>
`reportStepError` reported every step error as FAILURE, including a `context.Canceled` from a docker file-command read cancelled at job finalization — non-deterministic red CI. Classify `context.Canceled` as an interruption instead (deferring to the job context), so a genuine cancel reports cancelled and a stray teardown cancellation is ignored, never a failure.
---------
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1095
Reviewed-by: bircni <[email protected]>
Containers set `HostConfig.AutoRemove` but act also removes them explicitly, so the two removers race and the loser logs a 409 `removal of container X is already in progress` — seen at the end of nearly every `uses: docker://` step.
The explicit remove is redundant for `docker://` steps and docker actions (`Start(true)` already awaited exit), so it's skipped. Job and service containers keep both removers — their `sleep` entrypoint needs `AutoRemove` as a fallback reaper — so there the race is inherent and `remove()` now treats `NotFound` and `Conflict` as success.
---------
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1093
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
The `cancelled()`, `success()` and `failure()` expression functions dereferenced `Job.Status` unconditionally, so a nil `Job` context panicked the interpreter — which is why Gitea currently hands the runner a non-nil (empty) `JobContext` as a workaround. This routes all three through a `jobStatus()` helper that treats a nil `Job` as an empty status, keeping existing behaviour identical while removing the panic. Includes a regression test that panics on the old code and passes with the fix.
Related: https://github.com/go-gitea/gitea/pull/38495
Reviewed-on: https://gitea.com/gitea/runner/pulls/1092
Reviewed-by: Zettat123 <[email protected]>
Gitea 1.27 resolves `workflow_call` inputs server-side and sends natively typed JSON values in `github.event.inputs`, so a `type: boolean` input now arrives as a real JSON boolean. The runner coerced booleans by comparing the `any` value against the string `"true"`, which a native bool never matches, so every boolean input evaluated to `false` — including when the callee relied on its default, since the server pre-fills defaults into the event payload and the string fallback is never reached. This accepts a native bool and keeps the string comparison as a fallback, since `workflow_dispatch` inputs are still strings and YAML defaults decode to strings; servers before 1.27 never put a native bool in the payload, so they take the exact same code path as before.
The same coercion is applied to `setupWorkflowInputs` (locally-called reusable workflows, `uses: ./.gitea/workflows/x.yml`), where a `type: boolean` input was previously a native bool when passed as `with: { flag: true }` but a string when interpolated or taken from `default:`. It is now always a bool, matching GitHub, whose `inputs` context "preserves Boolean values as Booleans instead of converting them to strings". **This is potentially breaking**: `inputs.flag == 'true'` now evaluates to `false` and must become `inputs.flag == true`. That pattern is already false on GitHub (a bool compared to a string coerces to `1 == NaN`), but it works on Gitea today, so I am happy to split this hunk into its own PR if you would rather keep this one backport-safe.
Fixes#1082
Reviewed-on: https://gitea.com/gitea/runner/pulls/1087
Reviewed-by: Zettat123 <[email protected]>
Co-authored-by: Nicolas <[email protected]>
`SetOutputs` logged "ignore output because the value is too long" for values
larger than 1 MiB but then fell through and stored the value anyway, sending
it upstream via `UpdateTask`. The key-too-long branch directly above correctly
skips oversized keys with `continue`; this adds the same `continue` to the
value branch so the size guard is actually enforced and the log message
matches the behavior.
Adds regression coverage in `TestReporter_SetOutputs` for an oversized value
(dropped) and a value at exactly the 1 MiB limit (retained).
---------
Co-authored-by: Zettat123 <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1070
Reviewed-by: Zettat123 <[email protected]>
A batch of small, self-contained fixes and docs/example additions.
Fixes#625 - align the example config's `force_pull` with the actual default (`false`)
Fixes#804 - return an error instead of discarding `os.UserHomeDir()` when defaulting `cache.dir`/`host.workdir_parent`
Fixes#571 - send a `gitea-runner/<version>` User-Agent on API requests
Fixes#650 - detect an `Unauthenticated` fetch response and exit the daemon with an error instead of retrying forever
Fixes#766 - add `exec --eventpath` to supply a JSON event payload file
Fixes#256 - add a `bug-report` subcommand that prints version/Go/OS-arch/CPU info
Fixes#617 - add `runner.set_act_env` (default `true`) to optionally omit the `ACT=true` env var
Fixes#635 - record a failure result (and guard a nil reusable-workflow caller) when the job `if`-expression fails to evaluate
Fixes#1005 - remove README docs for config env-var overrides that were already removed from the code
Fixes#448 - clarify in `exec --job` help that `--workflows` may be needed to disambiguate
Fixes#209 - note that `host`-labelled runners still need Docker for `docker://` actions and service containers
Fixes#757 - add a systemd service example with automatic restart
Fixes#474 - add a Kubernetes StatefulSet example that persists the `.runner` registration across reschedules
Fixes#776 - build the basic (non-dind) docker image for `linux/riscv64`
Fixes#628 - build the basic (non-dind) docker image for `linux/s390x`Reviewed-on: https://gitea.com/gitea/runner/pulls/1075
Reviewed-by: Lunny Xiao <[email protected]>
Continuation of #362.
In addition to fixing merge conflicts and the `fmt.Errorf` issue from the previous version, I've also added a set of tests to cover some basic usage of `initInputs`
---------
Co-authored-by: Félix Baylac Jacqué <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1076
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: h7x4 <[email protected]>
GitHub Actions skips services whose image evaluates to an empty string, enabling conditional services via expressions like `image: ${{ matrix.image || '' }}`. Here such a service failed the job at `docker create`. Skip them before container creation, using GitHub's log message verbatim, and add a regression test.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1074
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: silverwind <[email protected]>
Refs https://gitea.com/gitea/runner/issues/155
The remaining failure mode described in the issue comments is the job container timing out when it tries to reach ACTIONS_CACHE_URL. The generated config now explains that cache.host/cache.port must be reachable from job containers, and calls out dockerized runners with auto-created per-job networks as a case that may need a fixed published cache endpoint or a shared Docker network
Co-authored-by: bircni <[email protected]>
Fixes: https://gitea.com/gitea/runner/issues/1061
Related: https://github.com/moby/moby/pull/52886
Docker 29.6.0 changed `dockerd` to shell out to the external `nft` binary for firewall-rule cleanup instead of linking `libnftables` (release note: *"Mitigate a crash in libnftables when using nftables as the firewall backend by changing the default build option to execute the `nft` command instead."*). The upstream `docker:dind` image only ships `iptables`/`ip6tables`, so `dockerd` logs `failed to find nft tool` on startup and shutdown.
Networking is unaffected (default firewall backend is still iptables), but the errors are noisy. Install `nftables` in both dind variants to provide the binary.
*PR authored by Claude (Opus 4.8).*Reviewed-on: https://gitea.com/gitea/runner/pulls/1064
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
A composite action's `RunContext` is **seeded with its parent's `Masks` slice** in `newCompositeRunContext`. After the composite finished running, the *entire* `compositeRC.Masks` (the seeded parent masks plus any added during the run) was appended straight back into the parent:
```go
rc.Masks = append(rc.Masks, compositeRC.Masks...)
```
Because the composite already contained the parent's masks, every composite action roughly **doubled** the parent's `Masks` length. With many nested/repeated composite actions this grows exponentially:
```
before=1152 after=2304 compositeLen=1152
before=2304 after=4608 compositeLen=2304
before=4608 after=9216 compositeLen=4608
...
```
### Impact
The bloated `Masks` slice made per-log-line secret redaction (`strings.makeGenericReplacer`, rebuilt per line since #1001) extremely slow. Log writes fell behind enough to keep subprocess pipes open after the process had already exited, surfacing as seemingly random CI failures — most often in later steps of mask-heavy workflows:
```
Error: exec: WaitDelay expired before I/O complete
```
The split of `cmd.Run()` into `cmd.Start()` + `cmd.Wait()` (#883) is what turned this long-standing latency into a hard failure.
### Fix
Only append masks that aren't already present in the parent, via a small `appendUniqueMasks` helper. This keeps the mask count bounded by the number of *distinct* secrets instead of growing exponentially.
I kept `Masks` as a `[]string` rather than switching to a set type, since it's passed by pointer to loggers across `logger.go`, `run_context.go`, and `runner.go` — a type change would be a much wider, riskier refactor. The `O(n·m)` dedup is harmless precisely because `n` now stays small.
Fixes #1054Reviewed-on: https://gitea.com/gitea/runner/pulls/1059
Reviewed-by: Lunny Xiao <[email protected]>
When `DEFAULT_ACTIONS_URL=self`, action clone URLs (`uses: owner/repo@ref`) are
built from the Gitea **AppURL** (`gitea_default_actions_url`), but
`shouldCloneURLUseToken` compared the clone URL host only against the runner's
**registered address** (`GitHubInstance`).
When the runner registers with a different hostname than AppURL — same instance,
different DNS (e.g. `gitea.local` vs `gitea.my-nas.lan`, internal vs external) —
the strict `u1.Host == u2.Host` check returns false, so the task token is **not**
attached and the action clone goes out anonymously. Against an instance with
`REQUIRE_SIGNIN_VIEW=true` this fails with:
```
Unable to clone https://gitea.example/owner/action refs/heads/v1: authentication required
```
The current workaround is to make the runner's registered host exactly match
`AppURL`. This PR removes the need for that.
Refs: https://github.com/go-gitea/gitea/issues/27933
## Change
- `shouldCloneURLUseToken` now trusts the clone URL when its host matches **either**
the registered instance (`GitHubInstance`) **or** the self-hosted default-actions
instance (`DefaultActionInstance`). Embedded basic auth is still rejected, and the
empty-host cases are unchanged.
- A new `Config.DefaultActionInstanceIsSelfHosted` flag gates the second candidate.
It is set in the daemon layer (`run/runner.go`, `exec.go`), where `github.com` and
a configured `GithubMirror` are distinguishable, so the token is **never** attached
for off-instance hosts.Reviewed-on: https://gitea.com/gitea/runner/pulls/1056
Reviewed-by: Zettat123 <[email protected]>
Co-authored-by: bircni <[email protected]>
## Summary
A nested `uses:` action inside a **local composite action** fails to clone with a bare `authentication required: Unauthorized` (401) when the instance resolves host-less action references against itself (`DEFAULT_ACTIONS_URL = self`). The job fails at the composite **main→post boundary**, even though the composite's own steps and all post-steps report success.
## Root cause
`newCompositeRunContext` nils `Config.Secrets` so composite steps do not see job secrets. But the action-clone path in `prepareActionExecutor` sourced its token via `getGitCloneToken` → `Config.GetToken()` → `Config.Secrets`, which is empty inside a composite RunContext. The nested action is therefore cloned anonymously → 401 against the authenticated instance.
Two details explain the exact symptom:
- It surfaces at the composite **main→post boundary** because the swallowed nested-step error is re-emitted by `common.JobError` at the end of the composite main pipeline.
- It carries **no clone URL** because, on a warm action cache, only `r.Fetch` runs (not `PlainClone`), and the go-git fetch error is returned verbatim.
## Fix
Source the clone token from `github.Token` instead of `Config.Secrets`. It is preserved across the composite config copy (`Config.Token` / `PresetGitHubContext`) and is identical to `Config.GetToken()` at the top level, so top-level and `act exec` behaviour is unchanged. The `shouldCloneURLUseToken` host gate is kept so the token is never sent to a foreign host. This also aligns the git-clone path with the ActionCache fetch path, which already uses `github.Token`.
Reusable workflows are unaffected — their RunContext keeps `Config.Secrets`.
## Before / after
Local composite action with a nested `uses:` (+ post step), followed by a marker step. Same workflow, same runner host — only the runner fix differs.
| Job step | Before fix | After fix |
| --- | --- | --- |
| `Run actions/checkout@v6` | ✅ success | ✅ success |
| `Run ./.gitea/actions/probe-composite` (nested `uses:` + post) | ❌ **failure** — bare 401 at the main→post boundary | ✅ success |
| `Step after composite` | ⊘ **skipped** | ✅ **ran** |
| Job result | ❌ **failed** | ✅ **succeeded** |
### Before — boundary log
```text
::endgroup::
##[error]authentication required: Unauthorized ← bare 401, no clone target
Run Post ./.gitea/actions/probe-composite
Success - Post ./.gitea/actions/probe-composite ← post steps run and succeed
Success - Post actions/checkout@v6
Job failed
```
The composite's own steps and both post-steps report `Success`; the job fails solely on the bare 401 emitted at the main→post boundary, and the next step is skipped.
### After — boundary log
```text
Run ./.gitea/actions/probe-composite
...composite steps...
Success - ./.gitea/actions/probe-composite
Run Marker after composite
PASSED composite boundary — no 401 (runner fix confirmed)
Success - Marker after composite
Job succeeded
```
## Reproduction
`.gitea/actions/probe-composite/action.yml`:
```yaml
name: probe-composite
runs:
using: composite
steps:
- uses: actions/setup-node@v6 # nested uses → has a post step
with: { node-version: 22 }
- run: echo "composite inner step OK"
shell: bash
```
`.gitea/workflows/probe.yaml`:
```yaml
on: [push]
jobs:
composite-boundary:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: ./.gitea/actions/probe-composite
- run: echo "step after composite" # skipped before the fix; runs after
```
## Verification
- **Before:** bare 401 at the boundary, reproduced with a `delay=0` tail — rules out a token-TTL / expiry effect; it is a missing credential.
- **After:** the composite step succeeds, the step after the composite runs, the job succeeds, and there is no `authentication required` in the log.
- A reusable workflow (`workflow_call`) with the same nested `uses:` + post step never hit the 401, which isolated the bug to the composite main/post path.
## Tests
Adds `TestStepActionRemoteCloneTokenSurvivesNilSecrets` (`act/runner`): asserts the clone token is forwarded when the RunContext mirrors a composite (`Secrets == nil`, token via `Config.Token`), and that the host gate still withholds the token for a foreign host. Verified to fail without the fix and pass with it.
---------
Reviewed-on: https://gitea.com/gitea/runner/pulls/1041
Reviewed-by: Zettat123 <[email protected]>
Co-authored-by: Christian Heim <[email protected]>
Co-committed-by: Christian Heim <[email protected]>
Fixes#1039 — local docker actions referenced with `uses: ./` are incorrectly served from a cached image built for a *different* repository.
## Root cause
For a local docker action, the built image tag is derived from `actionName`, which is the **workspace-relative** path of the action. For a self-referencing action that is always `"./"`, regardless of which repository it lives in. In `execAsDocker` this collapsed to the constant tag `act-dockeraction:latest` for *every* repository.
Before building, the runner checks `ContainerImageExistsLocally` and skips the rebuild if a matching tag is present. On a shared docker daemon, once repository A built its image, repository B's `uses: ./` found the same tag, skipped its rebuild, and **ran repository A's action image** — the reported "the first action is run again, because the path is cached", including the cross-repository concern raised in the issue.
## Fix
Extracted the tag computation into a small, testable `dockerActionImageTag(repository, actionName, localAction)` function. For local actions the tag is now namespaced with the repository (`path.Join(repository, actionName)`):
- different repositories no longer collide (`act-owner-repo-a-dockeraction:latest` vs `act-owner-repo-b-dockeraction:latest`);
- image caching is preserved within a repository (tag stays stable);
- remote actions are unchanged — they already carry a unique, ref-scoped `actionName` (the uses hash).
---------
Co-authored-by: Zettat123 <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1051
Reviewed-by: Zettat123 <[email protected]>
Co-authored-by: bircni <[email protected]>
Co-committed-by: bircni <[email protected]>
- fix Windows-only golangci-lint findings in `lp_windows.go` and process killer handle cleanup
- add a `lint-go-windows` target that runs golangci-lint with `GOOS=windows`
- include the Windows lint pass in `make lint` so Ubuntu CI covers `_windows.go` files
Reviewed-on: https://gitea.com/gitea/runner/pulls/1052
Reviewed-by: Zettat123 <[email protected]>
## Problem
When a workflow run is **cancelled**, the runner diverged from GitHub Actions:
- Main-stage `if: ${{ always() }}` / `if: ${{ cancelled() }}` steps **did not run** at all (unlike a *failed* run, where they do).
- `if: ${{ cancelled() }}` was **structurally impossible** to satisfy — it could never be true.
GitHub Actions runs `always()`/`cancelled()` steps (and post cleanup) even when a job is cancelled. This runner only honored that for action *post* steps (since #1016), leaving main-stage cleanup steps silently skipped.
## Root causes (both in `act/`)
1. **`getJobContext()`** derived the job status purely from step conclusions, so it could only ever return `"success"` or `"failure"`. Since `cancelled()` checks `Job.Status == "cancelled"`, it was impossible — and `success()` stayed *true* on a cancelled run, so the wrong `if` branch was taken everywhere.
2. **The main step pipeline** is chained with `Executor.Then()`, which short-circuits the moment `ctx.Err() != nil`. Once the server cancelled, every not-yet-started main step (including `always()` ones) was abandoned.
## Fix
- Add a per-`RunContext` `jobCancelled` flag + `markCancelled()`. `getJobContext()` now reports `"cancelled"` (taking precedence over success/failure), so `cancelled()`/`always()` are true and `success()`/`failure()` are false — matching GitHub's "only always()/cancelled() run on cancel" semantics.
- Replace the plain main-steps pipeline with `newMainStepsExecutor`. On interruption (`context.Canceled` from a server cancel, or `context.DeadlineExceeded` from the job timeout) it marks the job cancelled and runs the **remaining** steps under a fresh context (`context.WithoutCancel` + bounded timeout) so `always()`/`cancelled()` steps run for cleanup, while default-`success()` steps skip themselves. The original interrupt error is still propagated upward.
- Backstop `markCancelled()` in the post-step `Finally` so cancellations landing outside the main loop still surface the cancelled status to post steps.
Pre-steps keep normal short-circuit behavior, and reporting (`RESULT_CANCELLED`) is untouched — that remains handled by #1016.
## Reporting semantics (unchanged by this PR)
| Run state | failing post/`always()` step reported as |
| --------- | --------------------------------------------------------------------------------------------- |
| Normal | **FAILURE** |
| Timeout | **FAILURE** (deadline path preserves the job-error container) |
| Cancelled | **CANCELLED** — cancellation wins; the failing step is logged but doesn't flip the conclusion |
The new `always()` path runs under `context.WithoutCancel`, so the job-error container is preserved — a failing `always()` step records its failure at step level and does not panic in `SetJobError`.
Fixes#657
Reviewed-on: https://gitea.com/gitea/runner/pulls/1043
Reviewed-by: Zettat123 <[email protected]>
## Summary
When a workflow references a remote action (e.g. `uses: actions/checkout@v4`) the runner clones that repository during job setup.
Previously this was always a full clone(every branch and the complete history) even though only a single ref is needed.
This PR makes the runner shallow-clone the requested ref by default (`--depth=1`, single branch), falling back to a full clone when a shallow clone fails.
Notes:
- Existing on-disk caches are reused as-is; there is no forced re-clone on upgrade.
## Changes
- A new `runner.action_shallow_clone` option (default `true`) lets operators opt back into full clones.
- `cloneAtDepth`: attempt a shallow clone; fall back to a full clone when shallow clone fails.
- Keep a shallow cache cheap on update: fetch the single requested ref at depth 1 and skip `pull`.
---------
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1053
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
Co-committed-by: Zettat123 <[email protected]>
Fixes https://gitea.com/gitea/runner/issues/1047
The runner panics during shutdown when jobs are interrupted:
```
panic in executor: interface conversion: interface {} is nil, not map[string]error
...
gitea.com/gitea/runner/act/common.SetJobError(...)
/data/gitea/runner/act/common/job_error.go:27
gitea.com/gitea/runner/act/runner.reportStepError(...)
/data/gitea/runner/act/runner/job_executor.go:62
```
`SetJobError` did an unchecked type assertion on the job-error container stored in the context:
```go
ctx.Value(jobErrorContextKeyVal).(map[string]error)["error"] = err
```
When the container is absent — e.g. on shutdown, when a job is interrupted and `reportStepError` → `SetJobError` runs on a context where `WithJobErrorContainer` was never called — `ctx.Value` returns `nil` and asserting `nil.(map[string]error)` panics.
## Fix
Use the comma-ok form so a missing container is a no-op, matching the safe pattern already used in the sibling `JobError` function. If there's no container, there's nowhere to record the error anyway, so skipping is correct.
```go
func SetJobError(ctx context.Context, err error) {
if container, ok := ctx.Value(jobErrorContextKeyVal).(map[string]error); ok {
container["error"] = err
}
}
```
Reviewed-on: https://gitea.com/gitea/runner/pulls/1050
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: bircni <[email protected]>
Co-committed-by: bircni <[email protected]>
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| docker | stage | minor | `29.5.3-dind-rootless` → `29.6.0-dind-rootless` |
| docker | stage | minor | `29.5.3-dind` → `29.6.0-dind` |
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box
---
This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTEuMiIsInVwZGF0ZWRJblZlciI6IjQzLjE5MS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
Reviewed-on: https://gitea.com/gitea/runner/pulls/1044
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: Renovate Bot <[email protected]>
Co-committed-by: Renovate Bot <[email protected]>
Two `jobs.<job_id>` workflow syntax fields were parsed from YAML but silently ignored. This PR implements both:
- **`jobs.<job_id>.timeout-minutes`** — applies a context deadline around the entire job execution (container start, pre-steps, main steps, post-steps). Mirrors the existing step-level `evaluateStepTimeout`. Supports expression interpolation (e.g. `${{ env.MY_TIMEOUT }}`).
- **`jobs.<job_id>.continue-on-error`** — evaluates the expression when a job fails. If all failing matrix combinations had `continue-on-error: true`, the job does not cause the workflow run to fail (`handleFailure` skips it), and the tolerated failure reports `success` to dependent jobs through the `needs` context so jobs gated on the default `if: success()` still run (matching GitHub). The "any firm failure wins" rule is serialised under the existing per-job lock, so parallel matrix combinations are safe.
Both features follow the same patterns already used at the step level (`evaluateStepTimeout` / `isContinueOnError` in `act/runner/step.go`).
## Version compatibility
These changes are backward compatible. With mismatched versions the feature degrades silently to the previous behaviour (field ignored) — no errors on either side.
- `timeout-minutes`: runner-only, no server dependency.
- `continue-on-error`: requires both this runner PR and the matching Gitea server PR to take full effect. With only one side updated, the field continues to be ignored.
Related: [Github](https://github.com/go-gitea/gitea/pull/38100)
---------
Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1032
Reviewed-by: silverwind <[email protected]>
Reviewed-by: Zettat123 <[email protected]>
- Adds `runner.post_task_script` and `runner.post_task_script_timeout` (default `5m`) to run a host executable after each task’s built-in cleanup (post-steps, container teardown, bind-workdir removal).
- Stops task heartbeats via `Reporter.StopHeartbeats()` while the script runs so Gitea won’t assign overlapping work; the final task acknowledgement still happens in `reporter.Close()`.
- Script output goes to the runner process log; non-zero exits are warned only and do not change the job result.
- Documents lifecycle, offline behavior, timeouts, and Windows limits (`.ps1` not supported yet) in `docs/post-task-script.md`.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1026
Reviewed-by: Zettat123 <[email protected]>
Interpolate job container.volumes in GetBindsAndMounts(), matching service container volumes and other container fields (image, options).
Fixes expressions like ${{ secrets.MAME }}:/path:ro being passed literally and rejected as invalid bind mounts
Reviewed-on: https://gitea.com/gitea/runner/pulls/1036
Reviewed-by: Lunny Xiao <[email protected]>
Adds `ssh://` to the list of recognized URL schemes in `newRemoteAction`, so a
step can reference an action over SSH, e.g.:
```yaml
uses: ssh://[email protected]/actions/checkout@v4
```
Previously only `https://` / `http://` prefixes were parsed; an `ssh://` URL
fell through to the bare `org/repo` parser and failed.
### How auth works
SSH auth is delegated entirely to go-git's defaults — the runner configures no
SSH-specific options:
- **Which key?** go-git falls back to the host's **ssh-agent** (`$SSH_AUTH_SOCK`).
There is no key-file fallback, so the agent must hold a usable key. The SSH
**username** comes from the URL, so use `ssh://git@host/...` (a bare
`ssh://host/...` authenticates as an empty user and most servers reject it).
- **Host key trust?** Established out-of-band via the host's `known_hosts`
(`$SSH_KNOWN_HOSTS`, `~/.ssh/known_hosts`, `/etc/ssh/ssh_known_hosts`). The
runner host must already trust the remote; there is no accept-on-first-use.
- **Host key changes?** The clone fails with a host-key-mismatch error and stays
failed until `known_hosts` is updated on the host. Note `InsecureSkipTLS` does
**not** apply to SSH.
### Caching
The action cache path is derived from `{org}/{repo}` only (scheme/host are not
part of the key), so an `ssh://` action shares cache storage with the same
`org/repo` fetched over HTTP. This is unchanged by this PR and works in practice
(fetches resolve by SHA), but is worth noting.
### Tests
Adds `ssh://` cases to `Test_newRemoteAction` covering the scheme prefix, the
`git@` username placement, and a malformed-URL rejection. The agent/known_hosts
behavior lives in go-git and is not unit-tested here.
Fixes#841
Reviewed-on: https://gitea.com/gitea/runner/pulls/1035
Reviewed-by: Lunny Xiao <[email protected]>
Cancelling a job on a Linux/macOS host runner can leave the spawned process
tree running and hang the runner — the same failure mode fixed for Windows in
#1011, just on the other platforms.
Steps are launched as process-group leaders (`Setpgid`, or `Setsid` for the PTY
path), but the default `exec.CommandContext` cancellation only kills the
**direct child**. When a step launches a shell that starts a child which in turn
spawns further background processes, cancelling the job leaves the descendants
running. Because those orphans inherited the step's stdout/stderr pipe, the read
end never hits EOF and `cmd.Wait()` blocks forever.
Because the step executor never returns:
- the orphaned processes keep running (the cancelled work is not actually
stopped), and
- end-of-job cleanup is never reached, so the runner appears to go offline / stop
picking up jobs.
## Fix
Apply the same tree-kill approach as Windows, using the Unix counterpart of a Job
Object: the **process group**.
- Add a Unix `processKiller` (`process_unix.go`) that captures the step's PGID
(== PID, since the step is launched as a group leader) and sends `SIGKILL` to
the whole group on cancellation. This also closes the inherited pipe handles so
`cmd.Wait()` can return. `ESRCH` (group already gone) is not treated as an error.
- Restrict the previous no-op stub (`process_other.go`) to `plan9` and have it
fall back to a single-process kill, preserving plan9's prior behaviour.
- Wire `cmd.Cancel` (tree kill) and `cmd.WaitDelay` (10s) **unconditionally** in
`exec()` instead of Windows-only. `WaitDelay` also covers a step that
backgrounds a process holding the pipe open after the main process exits.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1025
Reviewed-by: Zettat123 <[email protected]>
## Problem
Several runner code paths could drop the **tail** of a step's log output, so a
failing (or cancelled) step would show output that is missing its last line(s).
This was observed in practice and traced to four independent issues.
## Root causes & fixes
### 1. Trailing line without a newline was never flushed
`common.lineWriter` buffers output until it sees a `\n`. A final line **without**
a trailing newline (e.g. an error message printed right before a process exits,
a panic, `printf` without `\n`) stayed in the internal buffer and was never
emitted — the writer exposed no flush at all.
- Added `lineWriter.Flush()` (idempotent), a `Flusher` interface, and a
`FlushWriter(io.Writer)` helper.
- Flush at every stream EOF: the exec copy goroutine, the container `attach()`
streaming goroutine, and at step end (`useStepLogger`).
### 2. Cancellation/timeout truncated output
`waitForCommand` returned immediately on `ctx.Done()` and abandoned the
output-copy goroutine, losing output the command had already produced. It now
drains with a bounded grace period before returning. The response channel is
buffered so the goroutine can't leak if the drain times out.
### 3. `attach()` raced the final bytes
Container output was streamed in a fire-and-forget goroutine that `wait()` did
not synchronize with, so the step could proceed before the last bytes were
written. `wait()` now blocks on the streaming goroutine (bounded) so output is
fully drained and flushed first.
### 4. `::stop-commands::` silently dropped lines from the step log
Lines between `::stop-commands::<token>` and its end token were echoed without
the `raw_output` field **and** short-circuited the handler chain (`return false`),
so they never reached the step log (non-raw entries aren't appended while a step
is running). Now returns `true` so they are still captured.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1028
Reviewed-by: Zettat123 <[email protected]>
Fixes#1023.
## Problem
In Windows host mode, a single stalled delete syscall (AV/EDR filter driver, unresponsive mount, dying disk) wedged the job forever at `Cleaning up container`. `HostEnvironment.Remove()` bounds every teardown phase (`terminateRunningProcesses`, both `removePathWithRetry` calls) except the `CleanUp` callback — an unbounded `os.RemoveAll(miscpath)` assigned in `startHostEnvironment`. The runner then held its capacity slot indefinitely, the task was reaped as a zombie, and there were no diagnostics.
## Fix
- **Bound the cleanup (availability):** `Remove()` now runs `CleanUp` under `hostCleanupTimeout` (30s) via `runWithTimeout`; on timeout it logs a warning and continues job completion. The stuck goroutine is left to finish (a delete syscall can't be interrupted). Added debug logs around the phase.
- **Reclaim the leak (disk hygiene):** a timed-out cleanup can leave a scratch dir behind, so the existing idle stale-dir sweep is extended to also remove orphaned host-mode scratch dirs (16-hex names) under `Host.WorkdirParent`, leaving the shared `tool_cache` and operator data untouched. The `bind_workdir` gate is dropped from `shouldRunIdleCleanup` so host-mode runners run the sweep.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1024
Reviewed-by: Lunny Xiao <[email protected]>
Updates `golang.org/x/crypto` from `v0.50.0` to `v0.52.0` (and `golang.org/x/net` from `v0.53.0` to `v0.54.0` as a transitive bump).
## Why
`make security-check` (govulncheck) reported **7 vulnerabilities**, all in `golang.org/x/crypto/ssh` at `v0.50.0`, reachable through the git action cache fetch path (`act/runner/action_cache.go` → `git.Remote.FetchContext`):
| ID | Issue |
| --- | --- |
| GO-2026-5013 | Byte arithmetic underflow/panic in `ssh` |
| GO-2026-5015 | Server panic during `CheckHostKey`/`Authenticate` |
| GO-2026-5017 | Client can cause server deadlock on unexpected responses |
| GO-2026-5018 | Pathological RSA/DSA parameters may cause DoS |
| GO-2026-5019 | Bypass of FIDO/U2F physical interaction |
| GO-2026-5020 | Infinite loop on large channel writes |
| GO-2026-5021 | Auth bypass via unenforced `@revoked` status in `knownhosts` |
All are fixed in `v0.52.0`.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1027
Reviewed-by: techknowlogick <[email protected]>
Completes the runner side of the cancellation flow, superseding #825. Two parts:
### 1. Report cancellations correctly (`fix`)
When `Reporter.Close` ran with the state still `UNSPECIFIED` and the reporter's
context had been cancelled, the synthesised final state attributed the job to
`RESULT_FAILURE` with an "Early termination" log row — misreporting a
cancellation as a generic failure. `Close` now detects the cancelled context
and finalizes the task as `RESULT_CANCELLED`.
### 2. Advertise the `cancelling` capability (`feat`)
[actions-proto-go v0.6.0](https://gitea.com/gitea/actions-proto-go) adds a
`capabilities` field to `RegisterRequest`/`DeclareRequest`, so the runner can
now tell the server it understands the transitional cancelling state:
- Bumps `gitea.dev/actions-proto-go` to `v0.6.0`.
- Adds a single `RunnerCapabilities()` source of truth exposing
`CapabilityCancelling`.
- Sends `Capabilities` on both register and declare.
With this the server records `HasCancellingSupport` and can rely on the runner
running post-step cleanup before a task is finalized as `RESULT_CANCELLED`.
## Compatibility
Wire-compatible against older servers: the new field uses a previously unused
field number (8 on `RegisterRequest`, 3 on `DeclareRequest`) and the client uses
the binary protobuf codec, so a server predating the field silently ignores it —
registration and declaration succeed and the feature simply stays off. It
activates only once both runner and server are on v0.6.0.
## Server side
The matching Gitea change (read `GetCapabilities()`, persist
`HasCancellingSupport`) is a separate PR against `gitea/gitea`.
Supersedes #825.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1016
Reviewed-by: Zettat123 <[email protected]>
Reviewed-by: wxiaoguang <[email protected]>
## Background
`DOCKER_USERNAME` and `DOCKER_PASSWORD` are commonly used by workflows as ordinary secrets for logging in to a private registry and pushing images. However, the runner also treated these secret names as implicit Docker pull credentials.
These credentials carry no registry information, but they were attached to every pull unconditionally. As a result, a user who configured `DOCKER_USERNAME` / `DOCKER_PASSWORD` secrets for their private registry (e.g. to push images) would have those same credentials sent to Docker Hub when pulling a public image, causing the pull to fail with authentication failure.
## Changes
- Stop using `DOCKER_USERNAME` and `DOCKER_PASSWORD` as implicit pull credentials for job containers.
- Stop injecting `DOCKER_USERNAME` and `DOCKER_PASSWORD` as pull credentials for step containers.
## ⚠️ BREAKING ⚠️
This is a breaking change.
Workflows or runner setups that previously relied on `DOCKER_USERNAME` and `DOCKER_PASSWORD` being implicitly used for Docker image pulls must migrate to an explicit authentication mechanism.
Migration options:
- For private job container images, use `container.credentials`:
```yaml
jobs:
build:
container:
image: registry.example.com/image:tag
credentials:
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
```
- For private service container images, use service `credentials`.
- For private `uses: docker://...` or private Docker actions, configure Docker authentication in the runner environment before the job starts. For example, run `docker login` on the runner host.
`DOCKER_USERNAME` and `DOCKER_PASSWORD` can still be used as ordinary workflow secrets, for example with `docker/login-action` before pushing images.
---
Related:
- Fixes#386
---------
Co-authored-by: Nicolas <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1007
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
Co-committed-by: Zettat123 <[email protected]>
- Add GitHub-style Actions **job summaries** support (writes to `GITHUB_STEP_SUMMARY` / `workflow/SUMMARY.md`) and render them in the run UI.
- Gitea stores summaries internally (DB) and serves them in the run view payload.
- `act_runner` uploads the summary **only when Gitea advertises support** (`X-Gitea-Actions-Capabilities: job-summary`), and warns on upload failures without failing the job.
## Compatibility
- New Gitea + old runner: no upload → no summary shown (no behavior change)
- New runner + old Gitea: capability not advertised → runner skips upload (no behavior change)
## Issue
- Fixesgo-gitea/gitea#23721
Reviewed-on: https://gitea.com/gitea/runner/pulls/917
Reviewed-by: Lunny Xiao <[email protected]>
Reviewed-by: Zettat123 <[email protected]>
## Background
Remote action cache directories can be keyed by the raw `uses` string. When Gitea's `DEFAULT_ACTIONS_URL` changes, the raw `uses` value may stay the same while the resolved clone URL changes.
In that case, an existing cached clone can still point to the old `origin` URL. Reusing it may fetch from the wrong remote with credentials for the new resolved URL, causing action clone failures until the user manually clears `~/.cache/act`.
## Changes
- Verify the cached clone's `origin` URL before reusing it in `CloneIfRequired`.
- Remove the cached clone and re-clone when the existing `origin` is different from the requested URL.
## Related
- Fixes#1010
Reviewed-on: https://gitea.com/gitea/runner/pulls/1014
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
Co-committed-by: Zettat123 <[email protected]>
## Problem
Cancelling a job on a Windows host runner can leave the spawned process
tree running and hang the runner. When a step launches a shell that
starts a child which in turn spawns further GUI/background processes,
cancelling the job kills only the direct child (the default
`exec.CommandContext` behaviour). The surviving descendants inherited
the step's stdout/stderr pipe, so the read end never hit EOF and
`cmd.Wait()` blocked forever.
Because the step executor never returned:
- the orphaned processes kept running (the cancelled work was not
actually stopped), and
- end-of-job cleanup (`Remove` → `terminateRunningProcesses`) was never
reached, so the runner appeared to go offline / stop picking up jobs.
`CREATE_NEW_PROCESS_GROUP` does not help here — it affects Ctrl-C signal
delivery, not handle inheritance or tree termination.
## Fix
- Assign each Windows step process to a **Job Object** immediately after
`cmd.Start()`. Descendants created afterwards are automatically part
of the job.
- Override `cmd.Cancel` to `TerminateJobObject`, so cancellation kills
the **entire descendant tree** atomically. This also closes the
inherited pipe handles, so `cmd.Wait()` can return.
- Set `cmd.WaitDelay` (10s) as a safety net: once the process has
exited, Wait force-closes the pipes and returns rather than blocking
forever — covering the case where the job-object setup fails (e.g.
nested-job restrictions), in which we fall back to the previous
single-process kill.
- The Job Object is created **without** `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`,
so closing the handle on normal completion does not kill legitimate
background processes; the tree is only torn down on explicit cancel.
Implemented behind `runtime.GOOS == "windows"` with a Windows-only
`processKiller` (Job Object) and no-op stubs elsewhere, so non-Windows
behaviour (default cancellation + `Setpgid`) is unchanged.
## Changes
- `act/container/process_windows.go` — Job Object `processKiller`
(create / assign / terminate).
- `act/container/process_other.go` — no-op stubs (`//go:build !windows`).
- `act/container/host_environment.go` — wire `cmd.Cancel` (tree kill)
and `cmd.WaitDelay` into `exec()`.
- `go.mod` / `go.sum` — promote `golang.org/x/sys` to a direct
dependency.
## Testing
I fully tested it already
## Notes
Follow-up to the Windows leftover-process reaping in #996: that sweep
now actually runs on cancellation because the step no longer hangs
before reaching it.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1011
Reviewed-by: techknowlogick <[email protected]>
`containerReference.id` was cached from `Create()` and never re-validated, so a container torn down out-of-band (AutoRemove on an unexpected exit, daemon-side cleanup, sibling-job race in a parallel matrix) left a stale id behind. The next `Copy`/`Exec` then hit the daemon with that dead id and failed the otherwise-successful job with `Could not find the file /var/run/act/ in container <id>`.
`find()` now `ContainerInspect`s the cached id and clears it only on a definitive `NotFound`; transient errors trust the cache so cleanup pipelines don't abort on a daemon blip. Operations that need a live container (`copyContent`/`copyDir`/`CopyTarStream`/`exec`/`GetContainerArchive`) fail fast with a clear `container "<name>" does not exist` instead of the daemon's generic empty-id error.
---
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/1003
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
`TestGetImagePullOptions` left docker/cli's process-global config dir pointed at `testdata/docker-pull-options` (which ships dummy `username:password` creds) via `config.SetDir`, without restoring it. Because that override is process-global, every later docker-gated test in the package then pulled with those creds — `TestDockerCopyToSymlinkPath`'s `alpine:latest` pull failed with `incorrect username or password` and broke CI. The workflow's `DOCKER_CONFIG` override can't mask this, since `SetDir` wins in-process.
Restore `config.Dir()` with `t.Cleanup`, and isolate the socket tests' leaks of the exported `CommonSocketLocations` and `DOCKER_HOST` behind an `isolateSocketEnv` helper.
Refs https://gitea.com/gitea/gitea.com/issues/83
---
This PR was written with the help of Claude Opus 4.8
Reviewed-on: https://gitea.com/gitea/runner/pulls/1004
Reviewed-by: Zettat123 <[email protected]>
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
Running the full suite under `-race` (dropping `-short`) exposed pre-existing data races in parallel matrix-job execution, fixed by not sharing mutable state across combinations:
- `containerDaemonSocket()`/`validVolumes()` derive per-job values instead of mutating shared `Config`
- `getWorkflowSecrets` builds a fresh map, `rc.steps()` clones each step, and go-git workdir access is serialized
- every write to a shared `Job`'s result/outputs runs under a per-`Job` lock, each combo interpolating outputs from a pristine snapshot (last wins, as on GitHub)
### Test suite
- capability gates (docker / network / host-tools / Linux) replace the `-short` skips, and the suite runs offline via local fixtures (the artifact flow uses an in-process loopback server, only the docker-action force-pull needs the network)
- drops redundant tests, adds a regression test for https://gitea.com/gitea/runner/issues/981 and a docker-in-docker harness (`make test-dind`)
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/994
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
## Summary
- When Gitea cancels a job, the reporter cancels its own task context; the final Close() flush then aborted on that same cancelled context and Gitea never received the runner's acknowledgement (missing tail logs and final state).
- On Windows the cancelled context also neutralised terminateRunningProcesses, leaving step grandchildren alive in the workspace, holding file handles, so the runner could no longer clean up and pick up new work.
- Reporter.Close() now flushes on a detached, bounded context via a new rpcCtx() helper and configurable Runner.ReportCloseTimeout (default 10s).
- terminateRunningProcesses now PowerShell-enumerates Win32_Process and taskkill /T /F's every process whose ExecutablePath or CommandLine references the job's workspace directories, on a detached context.
- The daemon heartbeat loop still exits on <-r.ctx.Done(): the runner is intentionally seen as offline by Gitea during cleanup so it isn't handed a new task overlapping the in-progress teardown.
## Test plan
- [x] go test ./internal/pkg/report/... ./act/container/ -run 'TestReporter_ServerCancelStillFlushesFinal|TestBuildWindowsWorkspaceKillScript'
- [x] make fmt && make lint-go - 0 issues
- [x] GOOS=windows go build ./... - clean
- [x] Manual on a Windows runner: trigger a long-running workflow, cancel from Gitea UI; verify (a) the job ends with tail logs + cancelled state in Gitea, (b) workspace cleans up, (c) the runner picks up a new job without restart.
Authored-by: bircni
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/996
Reviewed-by: silverwind <[email protected]>
## Summary
- Non-raw_output log entries above the globally configured `log.level` are no longer forwarded to the Gitea job log output
- Step output (`raw_output=true`) is always forwarded regardless of level — it is actual job stdout/stderr, not runner internals
- State-machine fields (`stepResult`, `jobResult`) are always processed regardless of level, preserving correct tracking for skipped steps (whose `stepResult` is emitted at `DebugLevel` in `step.go`)
- Extracts a `shouldAppendLogRow` helper to avoid repeating the combined `!duringSteps() && entry.Level <= log.GetLevel()` guard in three places
## Why not the approach in #677
PR #677 adds `if entry.Level != log.GetLevel() { return nil }` at the top of `Fire()`. That has two bugs:
1. Uses `!=` instead of `>`, so `Error`/`Fatal` entries are dropped when the configured level is `Warn`
2. Returns early before processing `stepResult`/`jobResult` state fields — skipped steps (whose `stepResult` is logged at `DebugLevel`) would never be marked complete
This fix instead applies the level guard only at the `r.logRows` append sites, leaving state tracking unconditional.
Relates to #409.
Reviewed-on: https://gitea.com/gitea/runner/pulls/989
Reviewed-by: Lunny Xiao <[email protected]>
Pin floating image tags:
- `golang` → `1.26-alpine3.23`
- `docker` dind variants → `29.5.2`
- `alpine` (basic stage + test fixture) → `3.23`
`ubuntu:24.04` and `scratch` left unchanged (no more-specific tag).
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/992
Reviewed-by: Lunny Xiao <[email protected]>
The `TestRunEvent*` integration tests are skipped in CI (`make test` runs `-short`), which hid several breakages that make them fail when run locally:
- `runTest` built the runner `Config` without `ContainerMaxLifetime`, so the job container ran `/bin/sleep 0` and exited immediately — every step failed with "container is not running". Set it to 1h.
- The root `.gitignore`'s unscoped `.env` and `dist` rules shadowed fixtures under `testdata/`. Anchored `dist` → `/dist` (the goreleaser output) and un-ignored `testdata/secrets/.env`.
- Added the missing `testdata/secrets/.env` fixture for `TestRunEventSecrets`.
- The `node24` local action referenced a `dist/index.js` bundle that was never committed (and was gitignored). Made the fixture self-contained (dependency-free ESM, `main: index.js`) so it runs without an `ncc` build. If you'd rather keep the `@actions/core`-based action and commit the built bundle instead, happy to switch.
Network-dependent subtests (remote `uses:`/composite actions) are out of scope.
---
This PR was written with the help of Claude Opus 4.7
---------
Co-authored-by: Nicolas <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/987
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
The teardown that removes a job's per-job network and container runs as a `Finally` on the step pipeline in `newJobExecutor`, which only executes after a successful start. When the start itself fails (e.g. a `docker cp` error from a buggy daemon), that `Finally` is skipped, so the network and container leak until Docker's address pool is exhausted and later jobs can no longer create networks.
This tears them down in `startContainer` when the start returns an error, reusing the existing `cleanUpJobContainer` teardown.
Exposed by the daemon regression in https://gitea.com/gitea/runner/issues/981, where every failed `docker cp` leaked a per-job network.
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/986
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
The runner already enforces Gitea v1.21+ (see the `connect.CodeUnimplemented` check in `daemon.go`), so several shims kept for v1.20 compatibility have been dead since 2023:
- `compatibleWithOldEnvs` — the `GITEA_DEBUG`, `GITEA_TRACE`, `GITEA_RUNNER_CAPACITY`, `GITEA_RUNNER_FILE`, `GITEA_RUNNER_ENVIRON`, `GITEA_RUNNER_ENV_FILE` env vars (superseded by the config file)
- `VersionHeader` (`x-runner-version`) and the `version` param of `client.New`
- `AgentLabels` field in `RegisterRequest` (replaced by `Labels`)
Also replaces a verbose `strings.TrimRightFunc` closure with `strings.TrimRight(s, "\r\n")` in the log row parser.
---
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/978
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
Adds `org.opencontainers.image.source` and `org.opencontainers.image.version` labels to all three image variants (`basic`, `dind`, `dind-rootless`).
- `source` lets tools like renovate retrieve release notes from the source repo.
- `version` exposes the build version on the image itself.
Both `release-tag` and `release-nightly` workflows pass `VERSION` as a build arg so the label reflects the actual git tag (or `git describe` output for nightly).
---
This PR was written with the help of Claude Opus 4.7
---------
Reviewed-on: https://gitea.com/gitea/runner/pulls/975
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
My builds kept flaking out with errors like `invalid format delimiter 'ghadelimiter_...' not found before end of file` or just strange failures in the complete job. After some digging I found an issue in `parseEnvFile` and have tested this fix against the test case presented.
- `parseEnvFile` reads `$GITHUB_ENV` / `$GITHUB_OUTPUT` with a `bufio.Scanner` using the default 64 KiB token size, and never checks `s.Err()`.
- Any action that writes a multi-line value with a single line >64 KiB silently aborts the scan with `bufio.ErrTooLong`, which surfaces as the misleading `"invalid format delimiter
'ghadelimiter_…' not found before end of file"`.
- Real-world trigger: `docker/build-push-action`'s `metadata` output embeds the full `GITHUB_EVENT_PATH` payload via buildx provenance; a long PR description (e.g. a Renovate dependency
table) puts the body field on one JSON-escaped line well past 64 KiB.
- Raise the scanner buffer to 1 MiB so realistic outputs parse.
### Reproduction
Test this in an action. This removes the `docker/build-push-action` aspect and reproduces it directly.
```yaml
jobs:
repro:
runs-on: ubuntu-latest
steps:
- id: big
run: |
{
echo 'value<<EOF'
head -c 70000 /dev/urandom | base64 -w0
echo
echo 'EOF'
} >> "$GITHUB_OUTPUT"
```
---------
Co-authored-by: Nicolas <[email protected]>
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/974
Reviewed-by: Lunny Xiao <[email protected]>
Reviewed-by: silverwind <[email protected]>
Co-authored-by: Jacob Alberty <[email protected]>
Co-committed-by: Jacob Alberty <[email protected]>
Removes code that whole-program reachability analysis (`deadcode` from `golang.org/x/tools`) confirmed unreachable, plus the `act/workflowpattern` package which no file outside its own directory imports.
- `act/common/draw.go` — CLI box-drawing helpers left over from nektos/act's dropped CLI
- `act/common/file.go` — `CopyFile`/`CopyDir` package-level helpers (container types have their own `CopyDir` methods, kept)
- `act/common/executor.go` — `Warning` type and `Warningf`. The `case Warning:` arm in `(Executor).Then`'s type switch was dead too (no code ever constructed a `Warning`); the switch is replaced with `if err != nil { return err }`
- `act/lookpath/env.go` — `LookPath` no-arg wrapper and `defaultEnv` struct. Only `LookPath2(file, env)` was used externally; the `Env` interface is kept
- `act/runner/action_cache_offline_mode.go` — `GoGitActionCacheOfflineMode` wrapper, never instantiated
- `act/workflowpattern/` — entire package, never imported
Net `-943` lines.
---
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/971
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
It displayed an unused log and start an unused go routine. We should check the executors number before continue.
```
INFO[2026-05-12T21:01:04-07:00] Running job with maxParallel=1 for 1 matrix combinations
INFO[2026-05-12T21:01:04-07:00] NewParallelExecutor: Creating 1 workers for 1 executors
INFO[2026-05-12T21:01:04-07:00] NewParallelExecutor: Creating 1 workers for 0 executors
INFO[2026-05-12T21:01:04-07:00] NewParallelExecutor: Creating 1 workers for 0 executors
```
Reviewed-on: https://gitea.com/gitea/runner/pulls/960
Reviewed-by: silverwind <[email protected]>
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Co-committed-by: Lunny Xiao <[email protected]>
Fixes#956.
Pseudo-TTY allocation is now an explicit, runner-wide opt-in via `runner.allocate_pty`, applied to both host and docker backends. Default is off, matching GitHub `actions/runner`.
```yaml
runner:
allocate_pty: false # default
```
**Before:** the host backend hardcoded `if true /* allocate Terminal */` and the docker backend used `term.IsTerminal(os.Stdout.Fd())`. As a result, `docker build` (and other TTY-aware tools) saw a TTY and emitted cursor-control redraw frames that flooded captured logs with thousands of duplicate-looking progress lines — only on host-mode runners in production, and on docker-mode runners when the daemon happened to be launched from a shell rather than a service.
**After:** both backends consult `Config.AllocatePTY`. The `term.IsTerminal` heuristic is gone, so behavior no longer depends on whether the daemon has a controlling terminal.
**Reproduction:** running `docker build` through `HostEnvironment.Exec` with output captured to a buffer:
| | Before (`if true`) | After (`AllocatePTY=false`) |
|---|---:|---:|
| bytes captured | 18,167 | 1,048 |
| ANSI CSI sequences | 556 | 0 |
| cursor-up `\e[1A` | 181 | 0 |
**Side fix:** `ptyWriter.AutoStop` is now `atomic.Bool`. The field is written from the exec goroutine after `cmd.Wait()` and read from the `copyPtyOutput` goroutine via `ptyWriter.Write`; existing tests never tripped the race detector because their commands produced no output before exit. The new host-mode test does.
---
This PR was written with the help of Claude Opus 4.7
---------
Co-authored-by: Lunny Xiao <[email protected]>
Co-authored-by: Nicolas <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/961
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
With this docker clients in workflows can connect on the default socket without needing to change DOCKER_HOST. Startup probe also removes the need for custom shell command.
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/709
Co-authored-by: thisisqasim <[email protected]>
Co-committed-by: thisisqasim <[email protected]>
Bumps `github.com/avast/retry-go` v4.7.0 -> v5.0.0, `golangci-lint` v2.11.4 -> v2.12.2 (aligns with gitea/gitea), and pins `govulncheck` to v1.3.0.
- `retry-go` v5 replaces the package-level `retry.Do(fn, opts...)` with a builder API `retry.New(opts...).Do(fn)`. The single call site in `internal/pkg/report/reporter.go` was migrated.
- `golangci-lint` v2.12.2 surfaces three new findings in `act/` (modernize/slicesbackward, govet/inline): one backward loop now uses `slices.Backward`, and the deprecated `reflect.Ptr` alias is replaced with `reflect.Pointer`.
- `go.mod`: the two direct-`require` blocks are merged into one, and a stray `gopkg.in/yaml.v3 // indirect` is moved into the indirect block. Purely cosmetic; `go.sum` is unchanged.
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/965
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
Fixes: https://gitea.com/gitea/runner/issues/859
Migration approach mirrors [actions-oss/act-cli#154](https://github.com/actions-oss/act-cli/pull/154).
### Dependency changes
- `github.com/docker/docker` v25.0.15 → **removed** (v29 doesn't exist as docker/docker; the project moved to moby/moby)
- `github.com/docker/cli` v25.0.7 → v29.4.3
- `github.com/docker/go-connections` v0.6.0 → v0.7.0
- `github.com/docker/docker-credential-helpers` v0.9.5 → v0.9.6
- `github.com/moby/go-archive` added at v0.2.0
- `github.com/moby/moby/api` added at v1.54.2
- `github.com/moby/moby/client` added at v0.4.1
- `github.com/moby/buildkit` removed (only used `dockerignore.ReadAll`, swapped for `moby/patternmatcher/ignorefile.ReadAll` directly)
- `github.com/containerd/errdefs` v0.3.0 → v1.0.0
### Migration
- v28: type aliases moved to their subpackages (`types.{Container,Image,Network,Exec}*` → `container/image/network/...`); deprecated APIs replaced (`ImageInspectWithRaw`, `client.IsErrNotFound`, `archive.CanonicalTarNameForPath`, `opts.ValidateMACAddress`, `ListOpts.GetAll`)
- v29: structural client redesign — every `cli.X(ctx, ...)` call switched to options-everywhere/Result-typed signatures, `ContainerExec*` → `Exec*`, `ContainerWait` returns a struct with `Result`/`Error` channels, `Tty`→`TTY`, `Copy*Container` takes options struct, `client.NewClientWithOpts` → `client.New`. `pkg/stdcopy` moved to `moby/moby/api/pkg/stdcopy`. The vendored copy of `cli/command/container/opts.go` was refreshed from cli v29 (now uses `netip.Addr` for IPs, port-set conversion helpers). A small local `parsePlatform` helper centralises the `os/arch[/variant]` parsing previously inlined into multiple call sites.
### Behaviour preservation
The migration introduced several behavioural shifts vs the v25 client; all were caught in review and reverted/fixed in follow-up commits:
- `GetDockerClient`: cli v29's `Ping(NegotiateAPIVersion: true)` returns errors that the old `NegotiateAPIVersion` silently swallowed. Restored best-effort behaviour (warn-log + continue) so daemons with blocked `_ping` or API < 1.40 keep working. The SSH-helper `client.New` call no longer inherits `client.FromEnv`, matching the old `NewClientWithOpts(WithHost, WithDialContext)` so `DOCKER_API_VERSION`/`DOCKER_TLS_VERIFY` don't leak into the SSH-tunneled client
- `parsePlatform`: malformed input now returns an explicit error instead of silently dropping to "no platform constraint" and pulling the host-default architecture. Single-segment (`"linux"`), 4+-segment (`"linux/arm/v7/extra"`), and trailing-slash (`"linux/arm/"`) inputs are all rejected
- `LoadDockerAuthConfig`/`LoadDockerAuthConfigs`: `config.LoadDefaultConfigFile(nil)` panics on a malformed config file (it does `fmt.Fprintln` on the nil `io.Writer`). Switched to `config.Load(config.Dir())` so load errors reach the logger and the panic path is gone. Restored the old behaviour of returning `config.Load` and `GetAuthConfig` errors to the caller (the v29 refactor had silently downgraded them to warn-only). A `reference.ParseNormalizedNamed` failure on the image string falls through to the `docker.io` default rather than aborting, since the old string-based hostname extraction was infallible
Test assertions also updated for two upstream error-message string shifts (`go-connections` port-range parser; `cli/opts` envfile BOM check). Added unit-test coverage for the new `parsePlatform` helper, locking in the intentional limits (single-segment, 4+-segment, and trailing-slash platforms rejected).
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/943
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
Fixes#957.
## Why
The runner builds a custom `http.Transport` for its RPC client. From first principles, once we stop using the default transport we also stop inheriting its default proxy resolution behavior, so `HTTP_PROXY`/`HTTPS_PROXY` are ignored unless we wire that behavior back explicitly.
## What
- set `Proxy: http.ProxyFromEnvironment` on the custom transport
- add a regression test that verifies `getHTTPClient` honors proxy environment variables
Reviewed-on: https://gitea.com/gitea/runner/pulls/962
Reviewed-by: ChristopherHX <[email protected]>
## Summary
- clone the runner environment map for each task before injecting runtime and OIDC tokens
- keep the shared base environment immutable so concurrent jobs cannot hit `concurrent map writes`
- add a unit test covering task-local env cloning
Fixes#958
---------
Co-authored-by: Nicolas <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/959
Reviewed-by: Nicolas <[email protected]>
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]>
## Summary
- `CopyCollector.WriteFile` now removes any existing destination file
before writing, handling read-only modes (e.g. git pack files at
`0444`) that cause `EACCES`/`ERROR_ACCESS_DENIED` on macOS and Windows.
- Added `O_TRUNC` to the `OpenFile` flags as a safety net.
## Root cause
When a composite action with a post step runs on a host runner,
`runPostStep` calls `maybeCopyToActionDir`, which re-copies the action
into `miscpath/act/actions/<name>/`. The first copy (main step) writes
`.git/objects/pack/*.idx` at the destination with mode `0444` (as set
by go-git). The second copy (post step) calls
`os.OpenFile(dest, O_CREATE|O_WRONLY, …)` on that existing `0444` file,
which fails immediately:
- macOS: `open <path>: permission denied`
- Windows: `open <path>: Access is denied`
Fixes: https://gitea.com/gitea/runner/issues/941
Fixes: https://gitea.com/gitea/runner/issues/876
---------
Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/942
Reviewed-by: silverwind <[email protected]>
Co-authored-by: Nicolas <[email protected]>
Co-committed-by: Nicolas <[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]>
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| docker | stage | major | `28-dind-rootless` → `29-dind-rootless` |
| docker | stage | major | `28-dind` → `29-dind` |
---
> ⚠️ **Warning**
>
> Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/856) for more information.
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box
---
This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNjAuNiIsInVwZGF0ZWRJblZlciI6IjQzLjE2MC42IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
---------
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/924
Reviewed-by: silverwind <[email protected]>
Co-authored-by: Renovate Bot <[email protected]>
Co-committed-by: Renovate Bot <[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]>
- Add idle-time cleanup for stale bind-workdir task directories instead of cleaning them on the task execution path.
- Make cleanup behavior configurable with `runner.startup_cleanup_age` as the stale-age threshold (default: `24h`) and `runner.idle_cleanup_interval` as the idle cleanup cadence (default: `10m`).
- Restrict cleanup scope to numeric task directory names only, to avoid touching operator-managed folders.
- Document the cleanup settings in `config.example.yaml` and `README.md`.
- Add tests for stale-directory cleanup, idle cleanup throttling, and config default/override parsing.
## Why
When a runner or host crashes, normal per-task cleanup may not run, leaving stale task directories under the bind-workdir root. Running this cleanup only while the runner is idle recovers that disk space without adding overhead to active job execution.
If you want, I can also tighten the wording around `startup_cleanup_age`, since the key name now reads a bit misleadingly relative to the actual behavior.
---------
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/870
Reviewed-by: silverwind <[email protected]>
## Summary
- Fix host-mode cleanup to remove the job **workspace** directory after a run (instead of leaving checkouts behind).
- On Windows, track step process PIDs and terminate remaining process trees during teardown before attempting workspace deletion (prevents file-lock failures).
- Skip workspace deletion when `bind_workdir` is enabled to avoid conflicting with runner-level task directory cleanup.
## Implementation details
- `HostEnvironment` now records PIDs for started commands and best-effort terminates them on Windows during `Remove()`.
- Workspace removal uses a small retry loop on Windows to handle transient locks.
- `BindWorkdir` is propagated into `HostEnvironment` so cleanup behavior matches runner configuration.
---------
Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/883
Reviewed-by: silverwind <[email protected]>
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [actions/hello-world-docker-action](https://github.com/actions/hello-world-docker-action) | action | major | `v1` → `v2` |
---
### Release Notes
<details>
<summary>actions/hello-world-docker-action (actions/hello-world-docker-action)</summary>
### [`v2`](https://github.com/actions/hello-world-docker-action/releases/tag/v2): Version v2
[Compare Source](https://github.com/actions/hello-world-docker-action/compare/v1...v2)
Update action to use the new environment file method for setting outputs.
</details>
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box
---
This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNjAuNCIsInVwZGF0ZWRJblZlciI6IjQzLjE2MC40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
Reviewed-on: https://gitea.com/gitea/runner/pulls/921
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: Renovate Bot <[email protected]>
Co-committed-by: Renovate Bot <[email protected]>
## Summary
- Lower the default `fetch_interval_max` from 60s to 5s to reduce job pickup latency for common single-runner deployments
- PR #819 optimized defaults for 200-runner fleets, regressing single-runner pickup time from ~2s to ~65s
- Most deployments use few runners; large fleets can still tune this value higher in their config
## Impact
| `fetch_interval_max` | 1 runner pickup | 200 runners idle |
| -------------------- | --------------- | ---------------- |
| 60s (previous) | up to **65s** | 3.3 req/s |
| **5s (new default)** | up to **5s** | 40 req/s |
Closes https://gitea.com/gitea/act_runner/issues/869
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/875
Reviewed-by: silverwind <[email protected]>
Co-authored-by: Bo-Yi Wu <[email protected]>
Co-committed-by: Bo-Yi Wu <[email protected]>
Removes 88 `nolint` directives (386 → 298) via mechanical, zero-regression cleanups:
- **38 `bodyclose`** in `act/artifactcache/handler_test.go`: replaced by `defer resp.Body.Close()` after each HTTP call.
- **21 dead directives** (`gocyclo`, `dogsled`, `contextcheck`): none of these linters are enabled in `.golangci.yml`, so the directives were doing nothing.
- **29 `testifylint`** directives whose underlying issues were addressed by mechanical rewrites:
- `assert.Nil(t, err)` → `assert.NoError(t, err)`
- `assert.NotNil(t, err)` → `assert.Error(t, err)`
- `assert.Equal(t, true/false, x)` → `assert.True/False(t, x)`
- `assert.Equal(t, 0, len(x))` → `assert.Empty(t, x)`
- `assert.Equal(t, N, len(x))` → `assert.Len(t, x, N)`
- `assert.Len(t, x, 0)` → `assert.Empty(t, x)`
Many `testifylint` directives still apply because they flag `require-error` (i.e. testifylint wants `require.NoError` instead of `assert.NoError` for early bail-out). That's a behavior change (fail-fast vs continue) and out of scope for this purely mechanical cleanup — those can be addressed in a follow-up. Same for `expected-actual`, `equal-values`, `error-is-as`, and the remaining `nilnil` / `unparam` / `forbidigo` / `staticcheck` / `goheader` / `dupl` directives.
`golangci-lint run` is clean. Tests pass for all touched packages.
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/864
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
Old `cloneLock` is a package-level `sync.Mutex` that serialized every action clone across all goroutines, regardless of target directory.
This PR replaces it with a `sync.Map` of per-directory mutexes keyed by `input.Dir`. Same-directory operations still serialize; different directories now clone in parallel.
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/866
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
Co-committed-by: Zettat123 <[email protected]>
## Bug fixes
- Fixed "file name too long" errors when running matrix jobs with many or
long-valued matrix parameters. Docker volume and container names are now
bounded to a safe length by hashing the full name with SHA-256, keeping
the result well within the filesystem `NAME_MAX` limit (255 bytes) even
after Docker appends its own suffixes (`-env`, `-network`, etc.).
## Upgrade notes
This change renames the Docker containers, volumes and networks created by
the runner. Resources created by a previous version will **not** be cleaned
up automatically after upgrade and will become orphans.
After upgrading, you can remove the legacy resources with:
```sh
# volumes
docker volume ls -q | grep -E '^GITEA-ACTIONS-TASK-[0-9]+_' | xargs -r docker volume rm
# networks
docker network ls --format '{{.Name}}' | grep -E '^GITEA-ACTIONS-TASK-[0-9]+_.*-network$' | xargs -r docker network rm
```
> **Note:** If multiple act_runner instances share the same Docker daemon,
> make sure no runner using the old version is running before executing the
> cleanup commands above.
Fixes#686
---------
Co-authored-by: Nicolas <[email protected]>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/853
Reviewed-by: silverwind <[email protected]>
Co-authored-by: Morgan Peyre <[email protected]>
Co-committed-by: Morgan Peyre <[email protected]>
Closes#848. Addresses [GHSA-82g9-637c-2fx2](https://github.com/go-gitea/gitea/security/advisories/GHSA-82g9-637c-2fx2) and the follow-up points raised by @ChristopherHX and @haroutp in that thread.
The change is breaking only for `cache.external_server` which uses auth via a pre-shared secret.
## How auth works now
1. **Runner starts** → opens the embedded cache server on `:port`. Loads / creates a 32-byte HMAC signing key in `<cache-dir>/.secret`.
2. **Runner receives a task** → calls `handler.RegisterJob(ACTIONS_RUNTIME_TOKEN, repository)` before the job runs, defers a revoker that removes the credential on completion. Registrations are reference-counted so a stray re-register cannot revoke a live job.
3. **Job container runs `actions/cache`** → the toolkit sends `Authorization: Bearer $ACTIONS_RUNTIME_TOKEN` on every management call (`reserve`, `upload`, `commit`, `find`, `clean`). The cache server's middleware looks the token up in the registered-jobs map: miss → 401; hit → the job's repository is injected into the request context.
4. **Repository scoping** — every cache entry is stamped with `Repo` on reserve; `find`, `upload`, `commit` all verify the caller's repo matches. A job in repo A cannot see or poison a cache entry owned by repo B, even when both reach the server over the same docker bridge. GC dedup also groups by `(Repo, Key, Version)` so one repo can't age out another.
5. **Archive downloads** — `@actions/cache` does not attach Authorization when downloading `archiveLocation`, so the `find` response is a short-lived HMAC-signed URL: `…/artifacts/:id?exp=<unix>&sig=<hmac>`, 10-minute TTL, signature binds `cacheID:exp`. Tampered, expired, or foreign-secret URLs get 401.
6. **Defence-in-depth** — `ACTIONS_RUNTIME_TOKEN` is added to `task.Secrets` so the runner's log masker scrubs it from step output.
## `cache.external_server` (standalone `act_runner cache-server`)
Operators set `cache.external_secret` to the same value on the runner config and the `act_runner cache-server` config. The `cache-server` then runs with bearer auth on the cache API and exposes a control-plane at `POST /_internal/{register,revoke}` (gated by the shared secret). The runner pre-registers each task's `ACTIONS_RUNTIME_TOKEN` with the remote server before the job runs and revokes it on completion. Same per-job auth + repo scoping as the embedded handler, just over the network.
`cache-server` refuses to start without `cache.external_secret`; runner config load also fails when `cache.external_server` is set without `cache.external_secret`.
## User-facing changes
- **One-time cache miss after upgrade.** Pre-existing entries in `bolt.db` have no `Repo` stamp and won't match any job — they'll be evicted by the normal GC. First job per cache key rebuilds its cache.
- **`cache.external_server` deployments must add `cache.external_secret`.** Breaking change for anyone running a standalone `act_runner cache-server`: set the same `cache.external_secret` in both the runner config and the cache-server config. Without it neither side starts.
- **No config changes required for the default setup.** Runners using the embedded cache server (the common case) keep working without any yaml edits; the auth mechanism is invisible to workflows.
---
This PR was written with the help of Claude Opus 4.7
---------
Co-authored-by: Nicolas <[email protected]>
Co-authored-by: Christopher Homberger <[email protected]>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/849
Reviewed-by: ChristopherHX <[email protected]>
## Summary
Mirrors the GitHub Actions runner behaviour where each `run:` step shows a collapsible **"Run \<command\>"** section containing the script, shell command, and environment variables before the actual step output.
### What changes
- **`pkg/runner/step_run.go`**: In `stepRun.main()`, two new executors are added to the pipeline:
1. `logRunGroupHeader()` — runs after `setupShellCommandExecutor()` (so `sr.cmdline` is already resolved). Emits a `::group::Run <step>` log entry followed by the interpolated script, the full shell command line, and the step's env vars (sorted, internal vars filtered out).
2. The existing execution function now has `defer rawLogger.Infof("::endgroup::")` so the group is closed after the step finishes, regardless of success or failure.
### Env var filtering
Internal runner vars are hidden (`GITHUB_*`, `GITEA_*`, `RUNNER_*`, `INPUT_*`, `PATH`, `HOME`) — only user-relevant vars are shown, matching what GitHub Actions displays.
### Example output
```
▼ Run cargo build
cargo build
shell: bash --noprofile --norc -e -o pipefail {0}
env:
CARGO_HOME: /home/runner/.cargo
CARGO_INCREMENTAL: 0
CARGO_TERM_COLOR: always
<actual build output>
```
---------
Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/847
Reviewed-by: silverwind <[email protected]>
Reviewed-by: ChristopherHX <[email protected]>
## Problem
In host executor mode, uses: docker://<image> step actions fail because
act/runner/step_docker.go always attaches the step container to the job
container's network namespace, which doesn't exist in host mode.
### Example
Run following job in host runner
```yaml
jobs:
test:
runs-on: ubuntu-latest-host
steps:
- uses: docker://alpine:3.20
with:
args: echo hello
```
```
Error:
failed to start container: Error response from daemon:
joining network namespace of container:
No such container: xxxxxx
```
This pr allows the docker step in the host mode
## Testing
I tested following steps on host runner and it worked
```yaml
- name: Test azure cli action in host mode
uses: azure/cli@v2
env:
RUNNER_OS: Linux
with:
inlineScript: echo "hello from azure cli"
- uses: docker://alpine:3.20
with:
args: echo hello
```
---------
Co-authored-by: Nicolas <[email protected]>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/857
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: Mirko Sekulic <[email protected]>
Co-committed-by: Mirko Sekulic <[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]>
Due to `NewGitCloneExecutor` fetching all the refs (rather than `GoGitActionCache`), if any refs move in a non-fast-forward fashion, this causes the entire action update to fail. As GitHub has special refs like `pull/N/merge` which are guaranteed to move in a non-fast-forward fashion, this leads actions from GitHub usually failing to update.
```
☁ git clone 'https://github.com/Mic92/update-flake-inputs-gitea' # ref=main
cloning https://github.com/Mic92/update-flake-inputs-gitea to /var/lib/gitea-runner/nix0/.cache/act/9b0155f2957ac84c749f9ecc8afaec823af5ef2e67a104ac655623aee12ca5b2
Non-terminating error while running 'git clone': some refs were not updated
```
With the repo https://github.com/Mic92/update-flake-inputs-gitea, you can notice that it only has a `main` branch that moves in a fast-forward fashion and no tags that could've been force pushed.
Fixes#726
---------
Co-authored-by: Michael Hoang <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/846
Reviewed-by: silverwind <[email protected]>
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: Michael Hoang <[email protected]>
Co-committed-by: Michael Hoang <[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]>
# Matrix Strategy: Support for Scalar Values and Template Expressions
## 🎯 Overview
This Pull Request implements support for **scalar values and template expressions** in matrix strategies. Previously, every matrix value had to be declared as a YAML array — a bare scalar like `os: ubuntu-latest` or `version: ${{ fromJSON(...) }}` caused silent failures. Now, scalars are automatically wrapped into single-element arrays, enabling the `${{ fromJSON(...) }}` pattern that is commonly used in GitHub Actions workflows for dynamic matrix generation.
## ⚠️ Semantic Changes
Two subtle behavior changes are introduced that are worth understanding:
### 1. Scalar matrix values now produce a 1-element matrix expansion
**Before this PR:** A scalar value like
```yaml
strategy:
matrix:
os: ubuntu-latest # no brackets
```
failed to decode as `map[string][]interface{}`, so `Matrix()` returned `nil`. The job ran **once, without any matrix context** (`matrix.os` was undefined).
**After this PR:** The same input is wrapped to `["ubuntu-latest"]`. The job runs **one matrix iteration** with `matrix.os = "ubuntu-latest"` set.
> **Impact on existing workflows:** Workflows that accidentally used bare scalars instead of arrays will now run with a matrix context where they previously did not. The functional result is usually the same, but `matrix.*` variables are now populated. This is considered an improvement, but is a semantic change.
---
### 2. Unevaluated template expressions fall back to a literal string value
The actual resolution of `${{ fromJSON(needs.job1.outputs.versions) }}` into an array happens **before** `Matrix()` is called — in `EvaluateYamlNode` inside `pkg/runner/runner.go`. This PR does not change that evaluation path.
If evaluation succeeds → the YAML node already contains a proper array → `Matrix()` decodes it normally.
If evaluation **fails or is skipped** (e.g., expression not yet resolved) → the scalar string is now wrapped as `["${{ fromJSON(...) }}"]` → the job runs **one iteration with the literal unexpanded string** as the matrix value.
> **Note:** The test `TestJobMatrix/matrix_with_template_expression` covers this fallback path, not the successful resolution path. The literal-wrap behavior is the fallback, not the primary mechanism for template expression support.
---
## 🔑 Key Changes
### 1. Flexible Matrix Decoding (`pkg/model/workflow.go`)
#### New Helper Function: `normalizeMatrixValue`
```go
func normalizeMatrixValue(key string, val interface{}) ([]interface{}, error)
```
- Converts a single matrix value to the expected array format
- Handles three cases:
1. **Arrays**: Pass through unchanged
2. **Scalars**: Wrap in single-element array (including template expressions)
3. **Invalid types** (maps, etc.): Return error to detect misconfigurations early
**Supported scalar types:**
- `string` — including unevaluated template expressions
- `int`, `float64` — numeric values
- `bool` — boolean values
- `nil` — null values
#### Enhanced `Matrix()` Method
**Before:**
```go
func (j *Job) Matrix() map[string][]interface{} {
if j.Strategy.RawMatrix.Kind == yaml.MappingNode {
var val map[string][]interface{}
if !decodeNode(j.Strategy.RawMatrix, &val) {
return nil
}
return val
}
return nil
}
```
**After:**
```go
func (j *Job) Matrix() map[string][]interface{} {
if j.Strategy == nil || j.Strategy.RawMatrix.Kind != yaml.MappingNode {
return nil
}
// First decode to flexible map[string]interface{} to handle scalars and arrays
var flexVal map[string]interface{}
err := j.Strategy.RawMatrix.Decode(&flexVal)
if err != nil {
// If that fails, try the strict array format
var val map[string][]interface{}
if !decodeNode(j.Strategy.RawMatrix, &val) {
return nil
}
return val
}
// Convert flexible format to expected format with validation
val := make(map[string][]interface{})
for k, v := range flexVal {
normalized, err := normalizeMatrixValue(k, v)
if err != nil {
log.Errorf("matrix validation error: %v", err)
return nil
}
val[k] = normalized
}
return val
}
```
**Key improvements:**
- Handles unevaluated template expressions as scalar strings
- Automatic conversion to array format for consistent processing
- Validation to catch nested maps and invalid configurations
- Fallback to strict array format decoding for backward compatibility
- Early nil check for strategy to prevent nil pointer panics
---
## 🎁 Use Cases
### Example 1: Template Expression in Matrix (primary motivation)
```yaml
jobs:
setup:
runs-on: ubuntu-latest
outputs:
versions: ${{ steps.version-map.outputs.versions }}
steps:
- id: version-map
run: echo "versions=[\"1.17\",\"1.18\",\"1.19\"]" >> $GITHUB_OUTPUT
test:
needs: setup
strategy:
matrix:
# EvaluateYamlNode resolves this to an array before Matrix() is called
version: ${{ fromJSON(needs.setup.outputs.versions) }}
runs-on: ubuntu-latest
steps:
- run: echo "Testing version ${{ matrix.version }}"
```
### Example 2: Mixed Scalar and Array Values
```yaml
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest] # Array — unchanged
go-version: 1.19 # Scalar — wrapped to [1.19]
node: [14, 16, 18] # Array — unchanged
runs-on: ${{ matrix.os }}
```
### Example 3: Single Value Matrix
```yaml
jobs:
deploy:
strategy:
matrix:
environment: production # Scalar — wrapped to ["production"]
runs-on: ubuntu-latest
```
---
## ✅ Benefits
- ✨ **Feature Parity with GitHub Actions**: Supports template expressions in matrix strategies
- 🔒 **Robust Validation**: Detects misconfigured matrices (nested maps) early
- 🔄 **Backward Compatible**: Existing array-based workflows continue to work
- 📝 **Clear Error Messages**: Helpful validation messages for debugging
- 🛡️ **Type Safety**: Handles all YAML scalar types correctly
---
## 🚀 Breaking Changes
**Technically none in a strictly backward-incompatible sense**, but two semantic changes exist (see [⚠️ Semantic Changes](#️-semantic-changes-read-before-merging) above):
1. Scalar matrix values now iterate (previously they caused a `nil` matrix / no iteration)
2. Unevaluated template strings now produce a literal iteration instead of skipping
Both changes are improvements in practice, but downstream users should be aware.
---
## 📚 Related Work
- Related Gitea Server PR: [go-gitea/gitea#36564](https://github.com/go-gitea/gitea/pull/36564)
- Enables dynamic matrix generation from job outputs
- Supports advanced CI/CD patterns used in GitHub Actions
## 🔗 References
- [GitHub Actions: Matrix strategies](https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs)
- [GitHub Actions: Expression syntax](https://docs.github.com/en/actions/learn-github-actions/expressions)
- [GitHub Actions: `fromJSON` in matrix context](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix)
Reviewed-on: https://gitea.com/gitea/act/pulls/149
Reviewed-by: silverwind <[email protected]>
Co-authored-by: Pascal Zimmermann <[email protected]>
Co-committed-by: Pascal Zimmermann <[email protected]>
## Background
#819 replaced the shared `rate.Limiter` with per-worker exponential backoff counters to add jitter and adaptive polling. Before #819, the poller used:
```go
limiter := rate.NewLimiter(rate.Every(p.cfg.Runner.FetchInterval), 1)
```
This limiter was **shared across all N polling goroutines with burst=1**, effectively serializing their `FetchTask` calls — so even with `capacity=60`, the runner issued roughly one `FetchTask` per `FetchInterval` total.
#819 replaced this with independent per-worker `consecutiveEmpty` / `consecutiveErrors` counters. Each goroutine now backs off **independently**, which inadvertently removed the cross-worker serialization. With `capacity=N`, the runner now has N goroutines each polling on their own schedule — a regression from the pre-#819 baseline for any runner with `capacity > 1`.
(Thanks to @ChristopherHX for catching this in review.)
## Problem
With the post-#819 code:
- `capacity=N` maintains **N persistent polling goroutines**, each calling `FetchTask` independently
- At idle, N goroutines each wake up and send a `FetchTask` RPC per `FetchInterval`
- At full load, N goroutines **continue polling** even though no slot is available to run a new task — every one of those RPCs is wasted
- The `Shutdown()` timeout branch has a pre-existing bug: the "non-blocking check" is actually a blocking receive, so `shutdownJobs()` is never reached on timeout
## Real-World Impact: 3 Runners × capacity=60
Current production environment: 3 runners each with `capacity=60`.
| Metric | Post-#819 (current) | This PR | Reduction |
|--------|---------------------|---------|-----------|
| Polling goroutines (total) | 3 × 60 = **180** | 3 × 1 = **3** | **98.3%** (177 fewer) |
| FetchTask RPCs per poll cycle (idle) | **180** | **3** | **98.3%** |
| FetchTask RPCs per poll cycle (full load) | **180** (all wasted) | **0** (blocked on semaphore) | **100%** |
| Concurrent connections to Gitea | **180** | **3** | **98.3%** |
| Backoff state objects | 180 (per-worker) | 3 (one per runner) | Simplified |
### Idle scenario
All 180 goroutines wake up every `FetchInterval`, each sending a `FetchTask` RPC that returns empty. Server handles 180 RPCs per cycle for zero useful work. After this PR: **3 RPCs per cycle** — one per runner.
> Note: pre-#819 idle behavior was already ~3 RPCs/cycle due to the shared `rate.Limiter`. This PR restores that property while also addressing the full-load case below.
### Full-load scenario (all 180 slots occupied)
All 180 goroutines **continue polling** even though no slot is available. Every RPC is wasted. After this PR: all 3 pollers are **blocked on the semaphore** — **zero RPCs** until a task completes.
> This is a scenario neither the pre-#819 shared limiter nor the post-#819 per-worker backoff handles — both still issue `FetchTask` RPCs when no slot is free. The semaphore is the only approach of the three that ties polling to available capacity.
## Why Not Just Revert to `rate.Limiter`?
Reverting would restore the serialized behavior but is not the right long-term fix:
- **`rate.Limiter` has no concept of available capacity.** At full load it still hands out tokens and issues `FetchTask` RPCs that can't be acted on. The semaphore blocks polling entirely in that case — zero wasted RPCs.
- **It composes poorly with adaptive backoff from #819.** A shared limiter and per-worker backoff pull in different directions.
- **N goroutines serializing on a shared limiter means N-1 of them exist only to wait in line.** A single poller expresses the same behavior more directly.
The semaphore approach ties polling to capacity explicitly: `acquire slot → fetch → dispatch → release`. That invariant becomes structural rather than emergent from a rate limiter.
## Solution
Replace N polling goroutines with a **single polling loop** that uses a buffered channel as a semaphore to control concurrent task execution:
```go
// New: poller.go Poll()
sem := make(chan struct{}, p.cfg.Runner.Capacity)
for {
select {
case sem <- struct{}{}: // Acquire slot (blocks at capacity)
case <-p.pollingCtx.Done():
return
}
task, ok := p.fetchTask(...) // Single FetchTask RPC
if !ok {
<-sem // Release slot on empty response
// backoff...
continue
}
go func(t *runnerv1.Task) { // Dispatch task
defer func() { <-sem }() // Release slot when done
p.runTaskWithRecover(p.jobsCtx, t)
}(task)
}
```
The exponential backoff and jitter from #819 are preserved — just driven by a single `workerState` instead of N per-worker states.
## Shutdown Bug Fix
Fixed a pre-existing bug in `Shutdown()` where the timeout branch could never force-cancel running jobs:
```go
// Before (BROKEN): blocking receive, shutdownJobs() never reached
_, ok := <-p.done // blocks until p.done is closed
if !ok { return nil }
p.shutdownJobs() // dead code when jobs are still running
// After (FIXED): proper non-blocking check
select {
case <-p.done:
return nil
default:
}
p.shutdownJobs() // now correctly reached on timeout
```
## Code Changes
| Area | Detail |
|------|--------|
| `Poller.runner` | `*run.Runner` → `TaskRunner` interface (enables mock-based testing) |
| `Poll()` | N goroutines → single loop with buffered-channel semaphore |
| `PollOnce()` | Inlined from removed `pollOnce()` |
| `waitBackoff()` | New helper, eliminates duplicated backoff logic |
| `resetBackoff()` | New method on `workerState`, also resets stale `lastBackoff` metric |
| `Shutdown()` | Fixed blocking receive → proper non-blocking select |
| Removed | `poll()`, `pollOnce()` private methods (-2 methods, -42 lines) |
## Test Coverage
Added `TestPoller_ConcurrencyLimitedByCapacity` which verifies:
- With `capacity=3`, at most 3 tasks execute concurrently (`maxConcurrent <= 3`)
- Tasks actually overlap in execution (`maxConcurrent >= 2`)
- `FetchTask` is never called concurrently — confirms single poller (`maxFetchConcur == 1`)
- All 6 tasks complete successfully (`totalCompleted == 6`)
- Mock runner respects context cancellation, enabling shutdown path verification
```
=== RUN TestPoller_ConcurrencyLimitedByCapacity
--- PASS: TestPoller_ConcurrencyLimitedByCapacity (0.10s)
PASS
ok gitea.com/gitea/act_runner/internal/app/poll 0.59s
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/822
Reviewed-by: silverwind <[email protected]>
Co-authored-by: Bo-Yi Wu <[email protected]>
Co-committed-by: Bo-Yi Wu <[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]>
## What
Add an optional Prometheus `/metrics` HTTP endpoint to `act_runner` so operators can observe runner health, polling behavior, job outcomes, and RPC latency without scraping logs.
New surface:
- `internal/pkg/metrics/metrics.go` — metric definitions, custom `Registry`, static Go/process collectors, label constants, `ResultToStatusLabel` helper.
- `internal/pkg/metrics/server.go` — hardened `http.Server` serving `/metrics` and `/healthz` with Slowloris-safe timeouts (`ReadHeaderTimeout` 5s, `ReadTimeout`/`WriteTimeout` 10s, `IdleTimeout` 60s) and a 5s graceful shutdown.
- `daemon.go` wires it up behind `cfg.Metrics.Enabled` (disabled by default).
- `poller.go` / `reporter.go` / `runner.go` instrument their existing hot paths with counters/histograms/gauges — no behavior change.
Metrics exported (namespace `act_runner_`):
| Subsystem | Metric | Type | Labels |
|---|---|---|---|
| — | `info` | Gauge | `version`, `name` |
| — | `capacity`, `uptime_seconds` | Gauge | — |
| `poll` | `fetch_total`, `client_errors_total` | Counter | `result` / `method` |
| `poll` | `fetch_duration_seconds`, `backoff_seconds` | Histogram / Gauge | — |
| `job` | `total` | Counter | `status` |
| `job` | `duration_seconds`, `running`, `capacity_utilization_ratio` | Histogram / GaugeFunc | — |
| `report` | `log_total`, `state_total` | Counter | `result` |
| `report` | `log_duration_seconds`, `state_duration_seconds` | Histogram | — |
| `report` | `log_buffer_rows` | Gauge | — |
| — | `go_*`, `process_*` | standard collectors | — |
All label values are predefined constants — **no high-cardinality labels** (no task IDs, repo URLs, branches, tokens, or secrets) so scraping is safe and bounded.
## Why
Teams self-hosting Gitea + `act_runner` at scale need to answer basic SRE questions that are currently invisible:
- How often are RPCs failing? Which RPC? (`act_runner_client_errors_total`)
- Are runners saturated? (`act_runner_job_capacity_utilization_ratio`, `act_runner_job_running`)
- How long do jobs take? (`act_runner_job_duration_seconds`)
- Is polling backing off? (`act_runner_poll_backoff_seconds`, `act_runner_poll_fetch_total{result=\"error\"}`)
- Are log/state reports slow? (`act_runner_report_{log,state}_duration_seconds`)
- Is the log buffer draining? (`act_runner_report_log_buffer_rows`)
Today operators have to grep logs. This PR makes all of the above first-class metrics so they can feed dashboards and alerts (`rate(act_runner_client_errors_total[5m]) > 0.1`, capacity saturation alerts, etc.).
The endpoint is **disabled by default** and binds to `127.0.0.1:9101` when enabled, so it's opt-in and safe for existing deployments.
## How
### Config
```yaml
metrics:
enabled: false # opt-in
addr: 127.0.0.1:9101 # change to 0.0.0.0:9101 only behind a reverse proxy
```
`config.example.yaml` documents both fields plus a security note about binding externally without auth.
### Wiring
1. `daemon.go` calls `metrics.Init()` (guarded by `sync.Once`), sets `act_runner_info`, `act_runner_capacity`, registers uptime + running-jobs GaugeFuncs, then starts the server goroutine with the daemon context — it shuts down cleanly on `ctx.Done()`.
2. `poller.fetchTask` observes RPC latency / result / error counters. `DeadlineExceeded` (long-poll idle) is treated as an empty result and **not** observed into the histogram so the 5s timeout doesn't swamp the buckets.
3. `poller.pollOnce` reports `poll_backoff_seconds` using the pre-jitter base interval (the true backoff level), and only when it changes — prevents noisy no-op gauge updates at the `FetchIntervalMax` plateau.
4. `reporter.ReportLog` / `ReportState` record duration histograms and success/error counters; `log_buffer_rows` is updated only when the value changes, guarded by the already-held `clientM`.
5. `runner.Run` observes `job_duration_seconds` and increments `job_total` by outcome via `metrics.ResultToStatusLabel`.
### Safety / security review
- All timeouts set; Slowloris-safe.
- Custom `prometheus.NewRegistry()` — no global registration side-effects.
- No sensitive data in labels (reviewed every instrumentation site).
- Single new dependency: `github.com/prometheus/client_golang v1.23.2`.
- Endpoint is unauthenticated by design and documented as such; default localhost bind mitigates exposure. Operators exposing externally should front it with a reverse proxy.
## Verification
### Unit tests
\`\`\`bash
go build ./...
go vet ./...
go test ./...
\`\`\`
### Manual smoke test
1. Enable metrics in `config.yaml`:
\`\`\`yaml
metrics:
enabled: true
addr: 127.0.0.1:9101
\`\`\`
2. Start the runner against a Gitea instance: \`./act_runner daemon\`.
3. Scrape the endpoint:
\`\`\`bash
curl -s http://127.0.0.1:9101/metrics | grep '^act_runner_'
curl -s http://127.0.0.1:9101/healthz # → ok
\`\`\`
4. Confirm the static series appear immediately: \`act_runner_info\`, \`act_runner_capacity\`, \`act_runner_uptime_seconds\`, \`act_runner_job_running\`, \`act_runner_job_capacity_utilization_ratio\`.
5. Trigger a workflow and confirm counters increment: \`act_runner_poll_fetch_total{result=\"task\"}\`, \`act_runner_job_total{status=\"success\"}\`, \`act_runner_report_log_total{result=\"success\"}\`.
6. Leave the runner idle and confirm \`act_runner_poll_backoff_seconds\` settles (and does **not** churn on every poll).
7. Ctrl-C and confirm a clean \"metrics server shutdown\" log line (no port-in-use error on restart within 5s).
### Prometheus integration
Add to \`prometheus.yml\`:
\`\`\`yaml
scrape_configs:
- job_name: act_runner
static_configs:
- targets: ['127.0.0.1:9101']
\`\`\`
Sample alert to try:
\`\`\`
sum(rate(act_runner_client_errors_total[5m])) by (method) > 0.1
\`\`\`
## Out of scope (follow-ups)
- TLS and auth on the metrics endpoint (mitigated today by localhost default; add when operators need external scraping).
- Per-task labels (intentionally avoided for cardinality safety).
---
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/820
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Bo-Yi Wu <[email protected]>
Co-committed-by: Bo-Yi Wu <[email protected]>
## Summary
Many teams self-host Gitea + Act Runner at scale. The current runner design causes excessive HTTP requests to the Gitea server, leading to high server load. This PR addresses three root causes: aggressive fixed-interval polling, per-task status reporting every 1 second regardless of activity, and unoptimized HTTP client configuration.
## Problem
The original architecture has these issues:
**1. Fixed 1-second reporting interval (RunDaemon)**
- Every running task calls ReportLog + ReportState every 1 second (2 HTTP requests/sec/task)
- These requests are sent even when there are no new log rows or state changes
- With 200 runners × 3 tasks each = **1,200 req/sec just for status reporting**
**2. Fixed 2-second polling interval (no backoff)**
- Idle runners poll FetchTask every 2 seconds forever, even when no jobs are queued
- No exponential backoff or jitter — all runners can synchronize after network recovery (thundering herd)
- 200 idle runners = **100 req/sec doing nothing useful**
**3. HTTP client not tuned**
- Uses http.DefaultClient with MaxIdleConnsPerHost=2, causing frequent TCP/TLS reconnects
- Creates two separate http.Client instances (one for Ping, one for Runner service) instead of sharing
**Total: ~1,300 req/sec for 200 runners with 3 tasks each**
## Solution
### Adaptive Event-Driven Log Reporting
Replace the recursive `time.AfterFunc(1s)` pattern in RunDaemon with a goroutine-based select event loop using three trigger mechanisms:
| Trigger | Default | Purpose |
|---------|---------|---------|
| `log_report_max_latency` | 3s | Guarantee even a single log line is delivered within this time |
| `log_report_interval` | 5s | Periodic sweep — steady-state cadence |
| `log_report_batch_size` | 100 rows | Immediate flush during bursty output (e.g., npm install) |
**Key design**: `log_report_max_latency` (3s) must be less than `log_report_interval` (5s) so the max-latency timer fires before the periodic ticker for single-line scenarios.
State reporting is decoupled to its own `state_report_interval` (default 5s), with immediate flush on step transitions (start/stop) via a stateNotify channel for responsive frontend UX.
Additionally:
- Skip ReportLog when `len(rows) == 0` (no pending log rows)
- Skip ReportState when `stateChanged == false && len(outputs) == 0` (nothing changed)
- Move expensive `proto.Clone` after the early-return check to avoid deep copies on no-op paths
### Polling Backoff with Jitter
Replace fixed `rate.Limiter` with adaptive exponential backoff:
- Track `consecutiveEmpty` and `consecutiveErrors` counters
- Interval doubles with each empty/error response: `base × 2^(n-1)`, capped at `fetch_interval_max` (default 60s)
- Add ±20% random jitter to prevent thundering herd
- Fetch first, sleep after ��� preserves burst=1 behavior for immediate first fetch on startup and after task completion
### HTTP Client Tuning
- Configure custom `http.Transport` with `MaxIdleConnsPerHost=10` (was 2)
- Share a single `http.Client` between PingService and RunnerService
- Add `IdleConnTimeout=90s` for clean connection lifecycle
## Load Reduction
For 200 runners × 3 tasks (70% with active log output):
| Component | Before | After | Reduction |
|-----------|--------|-------|-----------|
| Polling (idle) | 100 req/s | ~3.4 req/s | 97% |
| Log reporting | 420 req/s | ~84 req/s | 80% |
| State reporting | 126 req/s | ~25 req/s | 80% |
| **Total** | **~1,300 req/s** | **~113 req/s** | **~91%** |
## Frontend UX Impact
| Scenario | Before | After | Notes |
|----------|--------|-------|-------|
| Continuous output (npm install) | ~1s | ~5s | Periodic ticker sweep |
| Single line then silence | ~1s | ≤3s | maxLatencyTimer guarantee |
| Bursty output (100+ lines) | ~1s | <1s | Batch size immediate flush |
| Step start/stop | ~1s | <1s | stateNotify immediate flush |
| Job completion | ~1s | ~1s | Close() retry unchanged |
## New Configuration Options
All have safe defaults — existing config files need no changes:
```yaml
runner:
fetch_interval_max: 60s # Max backoff interval when idle
log_report_interval: 5s # Periodic log flush interval
log_report_max_latency: 3s # Max time a log row waits (must be < log_report_interval)
log_report_batch_size: 100 # Immediate flush threshold
state_report_interval: 5s # State flush interval (step transitions are always immediate)
```
Config validation warns on invalid combinations:
- `fetch_interval_max < fetch_interval` → auto-corrected
- `log_report_max_latency >= log_report_interval` → warning (timer would be redundant)
## Test Plan
- [x] `go build ./...` passes
- [x] `go test ./...` passes (all existing + 3 new tests)
- [x] `golangci-lint run` — 0 issues
- [x] TestReporter_MaxLatencyTimer — verifies single log line flushed by maxLatencyTimer before logTicker
- [x] TestReporter_BatchSizeFlush — verifies batch size threshold triggers immediate flush
- [x] TestReporter_StateNotifyFlush — verifies step transition triggers immediate state flush
- [x] TestReporter_EphemeralRunnerDeletion — verifies Close/RunDaemon race safety
- [x] TestReporter_RunDaemonClose_Race — verifies concurrent Close safety
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/819
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: Bo-Yi Wu <[email protected]>
Co-committed-by: Bo-Yi Wu <[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]>
The main Gitea docker image is already distributed with proper semver tags, such that users can pin to e.g. the minor version and still pull in patch releases. This is something that has been lacking on the act runner images.
This PR expands the docker image tag versioning strategy such that when `v0.2.13` is released the following image tags are produced:
Basic:
- `0`
- `0.2`
- `0.2.13`
- `latest`
DinD:
- `0-dind`
- `0.2-dind`
- `0.2.13-dind`
- `latest-dind`
DinD-Rootless:
- `0-dind-rootless`
- `0.2-dind-rootless`
- `0.2.13-dind-rootless`
- `latest-dind-rootless`
To verify the `docker/metadata-action` produces the expected results in a Gitea workflow environment I've executed a release workflow. Results can be seen in [this run](https://gitea.com/RoboMagus/gitea_act_runner/actions/runs/14). (Note though that as the repository name of my fork changed from `act_runner` to `gitea_act_runner` this is reflected in the produced docker tags in this test run!)
---------
Co-authored-by: RoboMagus <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Co-authored-by: techknowlogick <[email protected]>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/720
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: RoboMagus <[email protected]>
Co-committed-by: RoboMagus <[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]>
Related to https://github.com/go-gitea/gitea/pull/32562
Resolve https://gitea.com/gitea/act_runner/issues/102
To support using actions and workflows from private repositories, we need to enable act_runner to clone private repositories.
~~But it is not easy to know if a repository is private and whether a token is required when cloning. In this PR, I added a new option `RetryToken`. By default, token is empty. When cloning a repo returns an `authentication required` error, `act_runner` will try to clone the repo again using `RetryToken` as the token.~~
In this PR, I added a new `getGitCloneToken` function. This function returns `GITEA_TOKEN` for cloning remote actions or remote reusable workflows when the cloneURL is from the same Gitea instance that the runner is registered to. Otherwise, it returns an empty string as token for cloning public repos from other instances (such as GitHub).
Thanks @ChristopherHX for https://gitea.com/gitea/act/pulls/123#issuecomment-1046171 and https://gitea.com/gitea/act/pulls/123#issuecomment-1046285.
Reviewed-on: https://gitea.com/gitea/act/pulls/123
Reviewed-by: ChristopherHX <[email protected]>
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
Co-committed-by: Zettat123 <[email protected]>
Cannot guarantee that all noisy logs can be removed at once.
Comment them instead of removing them to make it easier to merge upstream.
What have been removed in this PR are those that are very very long and almost unreadable logs, like
<img width="839" alt="image" src="/attachments/b59e1dcc-4edd-4f81-b939-83dcc45f2ed2">
Reviewed-on: https://gitea.com/gitea/act/pulls/108
Reviewed-by: Lunny Xiao <[email protected]>
The current code overrides the container's entrypoint with `sleep`. Unfortunately, that prevents initialization scripts, such as to initialize Docker-in-Docker, from running.
The change simply moves the `sleep` to the command, rather than entrypoint, directive.
For most containers of this sort, the entrypoint script performs initialization, and then ends with `$@` to execute whatever command is passed.
If the container has no entrypoint, the command is executed directly. As a result, this should be a transparent change for most use cases, while allowing the container's entrypoint to be used when present.
Reviewed-on: https://gitea.com/gitea/act/pulls/86
Reviewed-by: Jason Song <[email protected]>
Co-authored-by: Thomas E Lackey <[email protected]>
Co-committed-by: Thomas E Lackey <[email protected]>
* Use same socket defaulting strategy every time
* Always default to DOCKER_HOST
* Add more debug logs
* Commenting, and massively simplified socket logic
* Rever to upstream run_context.go
* Fix EACCESS error regarding /opt/hostedtoolcache
* Revert "Fix EACCESS error regarding /opt/hostedtoolcache"
This reverts commit b2a8394d3358e1b5aab9dabe555d4a3f2bf0b2f9.
* Revert CLI debug logs
* Move socket and host handling to own function, and simplify logic
* Move to container package
* Make return be a struct
* Write tests to verify functionality
* Fix DOCKER_HOST being set to the string "DOCKER_HOST"
* Always use struct
* Use socketLocation, for DOCKER_HOST and more defaults
* Fixup arguments to GetSocketAndHost in test and root.go
* Un-struct hasDockerHost
* Fixup logic and set hasDockerHost
* Minor scoping & variable name change
* Move functionality to a new file
* Rename corresponding test
* Reviewfix
* Fix DOCKER_HOST expected
* Fix test assertions and add comments
* Swap comparison actual, expected
* Fixed no-DOCKER_HOST env test
* Fixed default socket test
* Add test to verify review comments
* Add more test for greater test coverage
* Consistent comment references
* Fix bug found while writing tests
* Passing tests
* NoMountNoHost testfix
* Rename test appropriately
* NoMount testfix
* Fixed OnlySocket
* Swap expected <-> actual in tests
---------
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
The toolcache on GitHub Actions need to be in
/opt/hostedtoolcache. This is the case for all
environment variables set by act, but it's not the
case for the volume mounted into the container.
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: ChristopherHX <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
- include repoURL and repoRef in error
- map NoErrAlreadyUptodate to `couldn't find remote ref` for branchOrtag
fetch request
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Prior to this commit, `parseEnvs` accept two parameters:
1. env []string
2. envs map[string]string
`parseEnvs` then do a `nil` check for `env`. However, we never pass a
`nil` `env` to `parseEnvs` in `newRunCommand`.
This commit simplify the `parseEnvs` function to accept just one
`env []string` parameter and return the result as `map[string]string`
instead.
Signed-off-by: Eng Zer Jun <[email protected]>
* WorkflowDispatchConfig supports ScalarNode and SequenceNode yaml node kinds
* Avoid using log.Fatal
* package slices is not in golang 1.20
---------
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Match cache restore-keys in creation reverse order
* Match full prefix when selecting cache
---------
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
NetBSD can run Docker CLI and then use Docker on some remote machine
via DOCKER_HOST.
(This can be probably extended to all other Unix-es capable of running
just Docker CLI.)
Co-authored-by: ChristopherHX <[email protected]>
Move the logging back up a level to fix a minor logging issue introduced in #2088
`RunContext`s for composite actions have dummy/blank `Job`s with no `runs-on`,
meaning their calls to `withGithubEnv` would result in an inaccurate log message
complaining that `'runs-on' key not defined in ...`
Co-authored-by: Jason Song <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
network-scoped alias is supported only for containers in user defined networks
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-11-12 18:09:25 +00:00
Jon JensenGitHubmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Support array expressions in runs-on
* Simplify appproach to use EvaluateYamlNode, fix case-sensitivity bug
---------
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Support services (#42)
Removed createSimpleContainerName and AutoRemove flag
Co-authored-by: Lunny Xiao <[email protected]>
Co-authored-by: Jason Song <[email protected]>
Reviewed-on: https://gitea.com/gitea/act/pulls/42
Reviewed-by: Jason Song <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
Co-committed-by: Zettat123 <[email protected]>
* Support services options (#45)
Reviewed-on: https://gitea.com/gitea/act/pulls/45
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
Co-committed-by: Zettat123 <[email protected]>
* Support intepolation for `env` of `services` (#47)
Reviewed-on: https://gitea.com/gitea/act/pulls/47
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
Co-committed-by: Zettat123 <[email protected]>
* Support services `credentials` (#51)
If a service's image is from a container registry requires authentication, `act_runner` will need `credentials` to pull the image, see [documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idservicesservice_idcredentials).
Currently, `act_runner` incorrectly uses the `credentials` of `containers` to pull services' images and the `credentials` of services won't be used, see the related code: https://gitea.com/gitea/act/src/commit/0c1f2edb996a87ee17dcf3cfa7259c04be02abd7/pkg/runner/run_context.go#L228-L269
Co-authored-by: Jason Song <[email protected]>
Reviewed-on: https://gitea.com/gitea/act/pulls/51
Reviewed-by: Jason Song <[email protected]>
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
Co-committed-by: Zettat123 <[email protected]>
* Add ContainerMaxLifetime and ContainerNetworkMode options
from: https://gitea.com/gitea/act/commit/b9c20dcaa43899cb3bb327619d447248303170e0
* Fix container network issue (#56)
Follow: https://gitea.com/gitea/act_runner/pulls/184
Close https://gitea.com/gitea/act_runner/issues/177
- `act` create new networks only if the value of `NeedCreateNetwork` is true, and remove these networks at last. `NeedCreateNetwork` is passed by `act_runner`. 'NeedCreateNetwork' is true only if `container.network` in the configuration file of the `act_runner` is empty.
- In the `docker create` phase, specify the network to which containers will connect. Because, if not specify , container will connect to `bridge` network which is created automatically by Docker.
- If the network is user defined network ( the value of `container.network` is empty or `<custom-network>`. Because, the network created by `act` is also user defined network.), will also specify alias by `--network-alias`. The alias of service is `<service-id>`. So we can be access service container by `<service-id>:<port>` in the steps of job.
- Won't try to `docker network connect ` network after `docker start` any more.
- Because on the one hand, `docker network connect` applies only to user defined networks, if try to `docker network connect host <container-name>` will return error.
- On the other hand, we just specify network in the stage of `docker create`, the same effect can be achieved.
- Won't try to remove containers and networks berfore the stage of `docker start`, because the name of these containers and netwoks won't be repeat.
Co-authored-by: Jason Song <[email protected]>
Reviewed-on: https://gitea.com/gitea/act/pulls/56
Reviewed-by: Jason Song <[email protected]>
Co-authored-by: sillyguodong <[email protected]>
Co-committed-by: sillyguodong <[email protected]>
* Check volumes (#60)
This PR adds a `ValidVolumes` config. Users can specify the volumes (including bind mounts) that can be mounted to containers by this config.
Options related to volumes:
- [jobs.<job_id>.container.volumes](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idcontainervolumes)
- [jobs.<job_id>.services.<service_id>.volumes](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idservicesservice_idvolumes)
In addition, volumes specified by `options` will also be checked.
Currently, the following default volumes (see https://gitea.com/gitea/act/src/commit/a72822b3f83d3e68ffc697101b713b7badf57e2f/pkg/runner/run_context.go#L116-L166) will be added to `ValidVolumes`:
- `act-toolcache`
- `<container-name>` and `<container-name>-env`
- `/var/run/docker.sock` (We need to add a new configuration to control whether the docker daemon can be mounted)
Co-authored-by: Jason Song <[email protected]>
Reviewed-on: https://gitea.com/gitea/act/pulls/60
Reviewed-by: Jason Song <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
Co-committed-by: Zettat123 <[email protected]>
* Remove ContainerMaxLifetime; fix lint
* Remove unused ValidVolumes
* Remove ConnectToNetwork
* Add docker stubs
* Close docker clients to prevent file descriptor leaks
* Fix the error when removing network in self-hosted mode (#69)
Fixes https://gitea.com/gitea/act_runner/issues/255
Reviewed-on: https://gitea.com/gitea/act/pulls/69
Co-authored-by: Zettat123 <[email protected]>
Co-committed-by: Zettat123 <[email protected]>
* Move service container and network cleanup to rc.cleanUpJobContainer
* Add --network flag; default to host if not using service containers or set explicitly
* Correctly close executor to prevent fd leak
* Revert to tail instead of full path
* fix network duplication
* backport networkingConfig for aliaes
* don't hardcode netMode host
* Convert services test to table driven tests
* Add failing tests for services
* Expose service container ports onto the host
* Set container network mode in artifacts server test to host mode
* Log container network mode when creating/starting a container
* fix: Correctly handle ContainerNetworkMode
* fix: missing service container network
* Always remove service containers
Although we usually keep containers running if the workflow errored
(unless `--rm` is given) in order to facilitate debugging and we have
a flag (`--reuse`) to always keep containers running in order to speed
up repeated `act` invocations, I believe that these should only apply
to job containers and not service containers, because changing the
network settings on a service container requires re-creating it anyway.
* Remove networks only if no active endpoints exist
* Ensure job containers are stopped before starting a new job
* fix: go build -tags WITHOUT_DOCKER
---------
Co-authored-by: Zettat123 <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Co-authored-by: Jason Song <[email protected]>
Co-authored-by: sillyguodong <[email protected]>
Co-authored-by: ChristopherHX <[email protected]>
Co-authored-by: ZauberNerd <[email protected]>
* fix: use actions/runner hashfiles in container
Previously hashfiles ran on the host,
this don't work for container generated content
* fix: lint
* fix: lint
* fix assign follow symlink flag
Co-authored-by: Jason Song <[email protected]>
---------
Co-authored-by: Jason Song <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* fix: fail if no stages were found
Adds a warning message if act is cannot find any stages to run
with the filters provided.
Reproduction:
- run `act -j gibberish`
Desired behavior: some indication I did something silly
Actual behavior: no output, just exit with success.
As a human who often makes spelling mistakes,
it would be nice if act warned me what I was doing that was silly
rather than exiting apparently doing
nothing with no obvious indication
I did something wrong.
* Revert "fix: fail if no stages were found"
This reverts commit 226adf1c15cf4c01d516a05dc923507e6999978d.
* fix: fail if no stages were found
Errors if no stages were found with the given filters.
Prints out a helpful error message, pointing users
in the right place for how to specify which stage to run.
Reproduction:
- run `act -j gibberish`
Desired behavior: some indication I did something silly
Actual behavior: no output, just exit with success.
As a human who often makes spelling mistakes,
it would be nice if act warned me what I was doing that was silly
rather than exiting apparently doing
nothing with no obvious indication
I did something wrong.
The `--device` option would do platform-dependent validation, but the
OS was not passed as an argument. When a user added the `--device` option
to the container, it would result in a "Unknown server OS" error.
From the Go docs:
"For a nil slice, the number of iterations is 0" [1]
Therefore, an additional nil check for `job.RunsOn()` before the loop is
unnecessary because `job.RunsOn()` returns a `[]string`.
[1]: https://go.dev/ref/spec#For_range
Signed-off-by: Eng Zer Jun <[email protected]>
* feat: CopyTarStream
Prepare for new process and thread safe action cache
* fix unused param
---------
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* prefer pwsh on windows if found
prefer bash over sh if found
One windows test no longer defines a default shell to test if it's pwsh
* add dep
---------
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Since actions can specify the download source via a url prefix. The prefix may contain some sensitive information that needs to be stored in secrets or variable context, so we need to interpolate the expression value for`uses` firstly.
Reviewed-on: https://gitea.com/gitea/act/pulls/75
Co-authored-by: sillyguodong <[email protected]>
Co-committed-by: sillyguodong <[email protected]>
* updates to support newer version of docker sdk
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 24.0.2+incompatible to 24.0.4+incompatible.
- [Commits](https://github.com/docker/cli/compare/v24.0.2...v24.0.4)
---
updated-dependencies:
- dependency-name: github.com/docker/cli
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot] <[email protected]>
* feat: upgrade to go 1.20
* feat: upgrade to go 1.20
* chore: use go version from go.mod
---------
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* throw if `uses` is invalid
* update JobType to return error
* lint
* put //nolint:dupl on wrong test
* update error message to remove end punctuation
* lint
* update remote job type check
* move if statement
* rm nolint:dupl ... we'll see how that goes
---------
Co-authored-by: Casey Lee <[email protected]>
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>Casey Lee
* fix: update name template in goreleaser
* fix: add arm version
* fix: space
* fix: --clean
---------
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Adds an explicit config option to specify the directory
below which action contents will be cached. If left empty
the previous location at `$XDG_CACHE_HOME/act` or
`$HOME/.cache/act` will be used respectively.
Required to resolvegitea/act_runner#235
Co-authored-by: Marius Zwicker <[email protected]>
Reviewed-on: https://gitea.com/gitea/act/pulls/65
Reviewed-by: Jason Song <[email protected]>
Co-authored-by: Marius Zwicker <[email protected]>
Co-committed-by: Marius Zwicker <[email protected]>
Adds an option to specify the directory below which actions
and host workspaces will be stored. If left empty the previous
location at $XDG_CACHE_HOME/act or $HOME/.cache/act will be used
respectively.
Co-authored-by: Casey Lee <[email protected]>
* feature: Add support for github action variables
* add --var flag for github variables
* unitests: Updated unittests to cover vars context.
* Remove syntax extension for vars and correct unit tests
* Update pkg/runner/expression.go
Co-authored-by: ChristopherHX <[email protected]>
---------
Co-authored-by: kuanyong-wong-partior <[email protected]>
Co-authored-by: ChristopherHX <[email protected]>
* Log incoming jobs.
Log the full contents of the job protobuf to make debugging jobs easier
* Ensure that the parallel executor always uses at least one thread.
The caller may mis-calculate the number of CPUs as zero, in which case
ensure that at least one thread is spawned.
* Use runtime.NumCPU for CPU counts.
For hosts without docker, GetHostInfo() returns a blank struct which
has zero CPUs and causes downstream trouble.
---------
Co-authored-by: Paul Armstrong <[email protected]>
Co-authored-by: Jason Song <[email protected]>
Follow: https://gitea.com/gitea/act_runner/pulls/184
Close https://gitea.com/gitea/act_runner/issues/177
#### changes:
- `act` create new networks only if the value of `NeedCreateNetwork` is true, and remove these networks at last. `NeedCreateNetwork` is passed by `act_runner`. 'NeedCreateNetwork' is true only if `container.network` in the configuration file of the `act_runner` is empty.
- In the `docker create` phase, specify the network to which containers will connect. Because, if not specify , container will connect to `bridge` network which is created automatically by Docker.
- If the network is user defined network ( the value of `container.network` is empty or `<custom-network>`. Because, the network created by `act` is also user defined network.), will also specify alias by `--network-alias`. The alias of service is `<service-id>`. So we can be access service container by `<service-id>:<port>` in the steps of job.
- Won't try to `docker network connect ` network after `docker start` any more.
- Because on the one hand, `docker network connect` applies only to user defined networks, if try to `docker network connect host <container-name>` will return error.
- On the other hand, we just specify network in the stage of `docker create`, the same effect can be achieved.
- Won't try to remove containers and networks berfore the stage of `docker start`, because the name of these containers and netwoks won't be repeat.
Co-authored-by: Jason Song <[email protected]>
Reviewed-on: https://gitea.com/gitea/act/pulls/56
Reviewed-by: Jason Song <[email protected]>
Co-authored-by: sillyguodong <[email protected]>
Co-committed-by: sillyguodong <[email protected]>
The runner's `privileged` config can be bypassed. Currently, even if the runner's `privileged` config is false, users can still enable the privileged mode by using `--privileged` in the container's option string. Therefore, if runner's config is false, the `--privileged` in options string should be ignored.
Reviewed-on: https://gitea.com/gitea/act/pulls/57
Reviewed-by: Jason Song <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
Co-committed-by: Zettat123 <[email protected]>
* chore: run act from cli on linux
To prevent issues like #1756 in the future, we need to
run act from the cli through all the root setup code.
This ensures that the basic CLI setup can succeed.
Co-authored-by: Björn Brauer <[email protected]>
* chore: set platform spec to use
---------
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: ChristopherHX <[email protected]>
* fix: try finding a socket, otherwise fail, respect user choice
* Update cmd/root.go
Co-authored-by: Jason Song <[email protected]>
* Update cmd/root.go
Co-authored-by: Jason Song <[email protected]>
---------
Co-authored-by: Jason Song <[email protected]>
This PR is to support overwriting the default `CMD` command of `services` containers.
This is a Gitea specific feature and GitHub Actions doesn't support this syntax.
Reviewed-on: https://gitea.com/gitea/act/pulls/50
Reviewed-by: Jason Song <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
Co-committed-by: Zettat123 <[email protected]>
act defaults network mode to "host", but when `--container-options` are
passed on the CLI, it uses the docker CLI options parser, which fills
empty values with defaults, in which case network mode is set to
"default".
Unless the user explicitly sets `--container-options="--network=xxx"`,
we should always default to "host", to keep act's behaviour.
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-18 14:37:59 +00:00
Jason SongGitHubmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* fix: add `server_url` attribute to github context
The `server_urL` attribute was missing in the `github` context.
Previously it was exposed as environment variable only.
Closes#1726
* fix: also set `api_url` and `graphql_url` attributes
* Make sure working directory is respected when configured from matrix
* Fix regression by setting Workingdirectory on stepRun instead of step or too early
Keep the order of jobs in the workflow file when parsing, and it will make it possible for Gitea to show jobs in the original order on UI.
Reviewed-on: https://gitea.com/gitea/act/pulls/33
Reviewed-by: Lunny Xiao <[email protected]>
And we will be able to use context like `${{ gitea.repository }}` in workflows yaml files, it's same as `${{ github.repository }}`
Reviewed-on: https://gitea.com/gitea/act/pulls/27
Reviewed-by: Zettat123 <[email protected]>
Close#21
I have tested this PR and run Go actions successfully on:
- Windows host
- Docker on Windows
- Linux host
- Docker on Linux
Before running Go actions, we need to make sure that Go has been installed on the host or the Docker image.
Reviewed-on: https://gitea.com/gitea/act/pulls/22
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Zettat123 <[email protected]>
Co-committed-by: Zettat123 <[email protected]>
* fix: correct ref and ref_name
The ref in the GitHub context is always full qualified
(e.g. refs/heads/branch, refs/tags/v1).
The ref_name is the ref with the strippep prefix.
In case of pull_requests, this is the merge commit ref
(e.g. refs/pull/123/merge -> 123/merge).
* test: update test data
* fix: github.job property is empty, GITHUB_JOB should be job id
fix: github.job property is empty #1621
fix: GITHUB_JOB should be the id not the name #1473
* fix linter problem.
---------
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* fix: map job output for reusable workflows
This fixes the job outputs for reusable workflows. There is
a required indirection. Before this we took the outputs from
all jobs which is not what users express with the workflow
outputs.
* fix: remove double evaluation
---------
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Change planner functions to return errors
This enables createStages to return `unable to build dependency graph`
Fix PlanEvent to properly report errors relating to events/workflows
* fix: GITHUB_ENV / PATH handling
* apply workaround
* add ctx to ApplyExtraPath
* fix: Do not leak step env in composite
See https://github.com/nektos/act/pull/1585 for a test
* add more tests
---------
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* allow overriding of GITHUB_ env variables
* bug fix for overriding env vars with empty string
* revert step.go
* refactor github_context to prevent lint failures. added more setters
* added ability to override github env variables
* handled base and head ref
- Join relative path and split dockerfile off to get context
Signed-off-by: Aidan Jensen <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* feat: step summary of test results
* fix: indent style
* fix: handle failed tests
* fix upload / create a logs artifact
* Update checks.yml
* fix: always upload logs
* fix: run success
* Move steps into a composite action
* use args and not the hardcoded ones
* format composite action
* format
* feat: add remote reusable workflows
This changes adds cloning of a remote repository to
run a workflow included in it.
Closes#826
* fix: defer plan creation until clone is done
We need wait for the full clone (and only clone once)
before we start to plan the execution for a remote workflow
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Use go-git to find remote URL
* Use go-git package to resolve HEAD revision (commit sha1)
* Use go-git to find checked-out reference
* Remove unused functions
* Prior to this change, the artifact server always binds to the detected
"outbound IP", breaks functionality when that IP is unroutable.
For example, Zscaler assigns the host a local CGNAT address,
100.64.0.1, which is unreachable from Docker Desktop.
* Add the `--artifact-server-addr` flag to allow override of the address
to which the artifact server binds, defaulting to the existing
behaviour.
Fixes: #1559
* refactor: remove docker reference filter
* make it work
* solve logic failure
* Another mistake
* another one
* revert signature of ImageExistsLocally
It is better to keep two return values
This commit adds a new `LoadDockerAuthConfigs` function, which loads all
registry auths that are configured on the host and sends them with the build
command to the docker daemon.
This is needed in case act builds a docker action and the images referenced in
that docker action are located on private registries or otherwise require
authentication (e.g. to get a higher rate limit).
The code is adapted from how the docker cli works:
https://github.com/docker/cli/blob/257ff41304bf121bdf1acdf00a1c7a896ed038d1/cli/command/image/build.go#L323-L332
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: Markus Wolf <[email protected]>
* test: define test case of path issues
Test case for #1528
* test: add multi arch grep
* fix: Always use current ExtraPath
* replace setup-node with run step
* Update push.yml
* yaml mistake
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-10 21:55:05 +00:00
Markus WolfGitHubmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Since reusable workflows are defining inputs and ouputs using the
on.workflow_call syntax, this could also be triggered by a workflow_call
event. That event does not exist within GitHub and we should make
sure our worklow is not called by that kind of 'synthetic' event.
See https://github.com/nektos/act/pull/1423/files/74da5b085c0c4d08c5e5bf53501e555cb585b26c#r1042413431
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-10 21:43:12 +00:00
Markus WolfGitHubmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* fix: preserve job result state in case of failure
There is just one job field for the job result. This is also true for
matrix jobs. We need to preserve the failure state of a job to
have the whole job failing in case of one permuation of the matrix failed.
Closes#1518
* test: remove continue-on-error on job level
This feature is not yet supported by act and if implemented
would make this test invalid
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Shouldn't provide token when cloning actions, the token comes from the instance which triggered the task, it might be not the instance which provides actions.
For GitHub, they are the same, always github.com. But for Gitea, tasks triggered by a.com can clone actions from b.com.
Reviewed-on: https://gitea.com/gitea/act/pulls/6
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Jason Song <[email protected]>
Co-committed-by: Jason Song <[email protected]>
* fix: ci snaphot job
* revert: deprecation of containerArchitecture
This option isn't part of parsed docker cli flags
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Support "result" on "needs" context.
This change adds "result" to a job's "needs" context, as documented [here](https://docs.github.com/en/actions/learn-github-actions/contexts#needs-context). `act` currently tracks the success/failure/cancelled status of a job, but does not include this value the `needs` context.
Fixes#1367
* Change `Needs` to use a new struct rather than the open type `interface{}`.
Related #1497Fixes#1367
* Add integration test to "needs" context change.
Relates: #1497
* feat: allow to spawn and run a local reusable workflow (#1423)
* feat: allow to spawn and run a local reusable workflow
This change contains the ability to parse/plan/run a local
reusable workflow.
There are still numerous things missing:
- inputs
- secrets
- outputs
* feat: add workflow_call inputs
* test: improve inputs test
* feat: add input defaults
* feat: allow expressions in inputs
* feat: use context specific expression evaluator
* refactor: prepare for better re-usability
* feat: add secrets for reusable workflows
* test: use secrets during test run
* feat: handle reusable workflow outputs
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* refactor: fix savestate in pre steps (#1466)
* refactor: fix savestate in pre steps
* fix pre steps collision
* fix tests
* remove
* enable tests
* Update pkg/runner/action.go
* Rename InterActionState to IntraActionState
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* fix: tail (not absolute) as entrypoint of job container (#1506)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Fix conflict in merge.
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* feat: allow to spawn and run a local reusable workflow
This change contains the ability to parse/plan/run a local
reusable workflow.
There are still numerous things missing:
- inputs
- secrets
- outputs
* feat: add workflow_call inputs
* test: improve inputs test
* feat: add input defaults
* feat: allow expressions in inputs
* feat: use context specific expression evaluator
* refactor: prepare for better re-usability
* feat: add secrets for reusable workflows
* test: use secrets during test run
* feat: handle reusable workflow outputs
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* fix: step env is unavailable in with property expr
* don't run the test on windows
* fix: composite action add missing shell
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* refactor: share UpdateFromEnv logic
* Add test for GITHUB_OUTPUT
Co-authored-by: Ben Randall <[email protected]>
* Add GITHUB_STATE test
* Add test for the old broken parser
Co-authored-by: Ben Randall <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* refactor: move autoremove into the jobexecutor
breaking: docker container are removed after job exit
* reduce complexity
* remove linter exception
* reduce cyclic complexity
* fix: always allow 1 min for stopping and removing the runner, even if we were cancelled
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* feat: interpolate the step names
Step names could contain expressions refering to event data.
Fixes#1353
* test: add missing mock data
* fix: setup composite expression evaluator
The RunContext does contain a cached ExpressionEvaluator.
This should be the case the composite RunContext as well.
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Casey Lee <[email protected]>
* feat: set-state and set-output file commands
* increase test timeout from 10m to 15m
* Prepare for HostExecutor PR
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* fix: keep path to event json file in composite actions
The event.json paths need to be copied over, since it the
GithubContext is recreated from the composite RC. And that
does read some value for the event file if available.
* test: add test case
* test: paste the test correctly and revert a line
Co-authored-by: ChristopherHX <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This change does parse the different types of workflow jobs.
It is not much by itself but the start to implement reusable
workflows.
Relates to #826
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
The io/ioutil package has been deprecated as of Go 1.16 [1]. This commit
replaces the existing io/ioutil functions with their new definitions in
io and os packages.
[1]: https://golang.org/doc/go1.16#ioutil
Signed-off-by: Eng Zer Jun <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* test: check workflow_dispatch inputs
This implements a test to check for `workflow_dispatch` inputs.
This will be a prerequisite for implementing the inputs.
* feat: map workflow_dispatch input to expression evaluator
This changes adds the workflow_dispatch event inputs
to the `inputs` context and maintaining the boolean type
* fix: coerce boolean input types
* fix: use step env if available, rc env otherwise
* test: add test for networking setup in act
This test makes sure that the hostname inside of act is resolvable.
* fix: only merge existing container options
When merging parsed container options without options being
set in a job, the default docker options are returned and
will override the expected defaults by act (e.g. network mode).
This is a first attempt to mitigate this behavior and only
merge settings if something was requested on a job.
* refactor: split config merging into own function
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
The env for remote composite actions need to be re-evaluated
at every stage (pre, main, post) as it is created during the
pre stage but there might be changes used as input to this
actions main stage (e.g. outputs for another action).
This is not required for local actions as their env is created
for the main stage (there is no pre stage).
Post stages do not need an updated env since they cannot recieve
inputs from other actions.
* fix: support docker create arguments from container.options (#1022)
* fix processing of errors, add verbose logging, fix test
* disable linter for code copied from docker/cli
* fix all linter issues
* Add license info
* Add opts_test.go from docker/cli and required testdata
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* test: add test case for #1319
* fix: setup of composite inputs
This change fixes the composite action setup handling of inputs.
All inputs are taken from the env now. The env is composed of
the 'level above'.
For example:
- step env -> taken from run context
- action env -> taken from step env
- composite env -> taken from action env
Before this change the env setup for steps, actions and composite
run contexts was harder to understand as all parts looked into
one of these: parent run context, step, action, composite run context.
Now the 'data flow' is from higher levels to lower levels which should
make it more clean.
Fixes#1319
* test: add simple remote composite action test
Since we don't have a remote composite test at all
before this, we need at least the simplest case.
This does not check every feature, but ensures basic
availability of remote composite actions.
* refactor: move ActionRef and ActionRepository
Moving ActionRef and ActionRepository from RunContext into the
step, allows us to remove the - more or less - ugly copy operations
from the RunContext.
This is more clean, as each step does hold the data required anyway
and the RunContext shouldn't know about the action details.
* refactor: remove unused properties
* fix: show workflow info even if on.push is not defined (#1329)
To fix listing of workflows in such cases list/graph filtering was split with planning.
Now act supports one of the following list (-l)/graph (-g) cases:
* show all jobs of loaded workflows: act -l
* show specific job JOBNAME: act -l -j JOBNAME
* show jobs of loaded workflows in which event EVENTNAME is set up: act -l EVENTNAME
* show jobs of loaded workflows in which first defined workflow event is set up: act -l --detect-event
For planning it supports:
* running specific job JOBNAME with triggered event determined from:
** CLI argument: act -j JOBNAME EVENTNAME
** first defined in loaded workflows event: act -j JOBNAME --detect-event
** only defined in loaded workflows event: act -j JOBNAME
** push event by default: act -j JOBNAME
* running jobs of loaded workflows in which event is set up, event is determined from:
** CLI argument: act EVENTNAME
** first defined in loaded workflows event: act --detect-event
** only defined in loaded workflows event: act
** push event by default: act
Except #1329 this PR fixes#1332, #1318
* Update docs/help
For log processing of the JSON logs, we want to be able to know which
keys/values of the matrices were used.
This commit adds the current matrix map to the job logger.
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: Markus Wolf <[email protected]>
* fix: align github.ref to GitHub Action
The `github.ref` value should be `refs/heads/branch` in most cases.
There are a few exceptions handled by the code.
This change prefixes the default case with `refs/heads` and adds
tests for this and the excpetional cases.
* fix: correct existing assertions
* refactor: ignore already closed error
This hides the `file already cloesd` error as it is
distracting in the output and does not provide any value.
* refactor: use go errors
Co-authored-by: Casey Lee <[email protected]>
This fixes an issue in the chain of post steps introduced
while updating the step-logger for composite actions.
It includes a test case so we make sure this does not happen
again.
* walk submodule path instead of dir name
* use file path instead of relative path
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
when running nested composite actions, step ids were repeating
leading to errors in parsing the output. this patch adds the
parent step id to ste stepID field.
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Made env interpolated instead of evaluated.
* [skip ci] Add Test Workflow file
* Activate Test
* fix Test
Co-authored-by: ChristopherHX <[email protected]>
currently build fail with:
```
run golangci-lint
Running [/home/runner/golangci-lint-1.47.0-linux-amd64/golangci-lint run --out-format=github-actions] in [] ...
Error: G112: Potential Slowloris Attack because ReadHeaderTimeout is not configured in the http.Server (gosec)
```
for example in this PR:
https://github.com/nektos/act/runs/7405009660?check_suite_focus=true
this sets the required ReadHeaderTimeout
* fix: the number in the github event is of type number
The go %s formattig option outputs the type if the given
input value is not of type string.
* test: update test data as well
* fix: use floats
This change stops act from rejecting valid entries such as
```
timeout-minutes: ${{ matrix.runtime == 'v8' && 30 || 15 }}
```
at the job level.
This change complements the fix that was already in place
for the Step struct, done in #1217. See:
https://github.com/nektos/act/commit/52f5c4592cbbf2e4dde3b35ad42f4e7475ad52df
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Added documentation on how to pass inputs for workflows that require them
* Added the correct command to trigger the workflow
Co-authored-by: Casey Lee <[email protected]>
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>Casey Lee
* feat(#1161): add --through-action to assigned actions from GitHub
* docs(flags): add --through-action and --through-action-token flags description
* test(action, remote): add test case for ThroughAction
* refactor(command): rename command from --through-action to --actions-from-github
* refactor(command): rename command from --actions-from-github to --replace-ghe-action-with-github-com
* Added tests for mid-size and big artifacts, reproducing a problem with chunked uploads.
* Added support for chunked uploads.
* Enforced overwriting uploaded artifacts on receiving the first chunk.
Co-authored-by: Casey Lee <[email protected]>
Do not initialize inputs a second time (in pre and main step).
When the action setup already run during pre step, we must not
re-run it during the main step, otherwise the inputs will be
overwritten by possible other action inputs.
* feat: use logger from context wherever possible
Co-authored-by: Markus Wolf <[email protected]>
* feat: add step/job id and results to json logs
Co-authored-by: Markus Wolf <[email protected]>
* test: value to be masked should not be hard-coded in the action
Co-authored-by: Markus Wolf <[email protected]>
* fix: replace values following ::add-mask:: in evaluated strings
Co-authored-by: Markus Wolf <[email protected]>
* feat: [DEBUG] identifier for debug logs to distinguish them
Co-authored-by: Markus Wolf <[email protected]>
* feat: replace logger with step logger
The container gets injected a job logger, but during the time that steps
are run, we want to use the step logger.
This commit wraps pre/main/post steps in an executor that replaces the
job logger with a step logger.
Co-authored-by: Markus Wolf <[email protected]>
* feat: add pre/post stage identifier fields to json log output
Co-authored-by: Markus Wolf <[email protected]>
* feat: add job/step result status to skipped steps/jobs
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* fix: skip local actions pre step in any case
We should skip local actions pre step, as it is not supported by github.
In turn we may need to late prepare remote actions which are run
as steps in a local composite action.
Fixes#1193
* test: remove obsolete test case
Since local actions does not run any pre-step anymore we don't test this case.
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
There might be use cases where users want to use GitHub's variables in
the environment variables, which is a valid use case.
This commits adds support for replacement of GitHub's env with GitHub's
values.
Signed-off-by: André Martins <[email protected]>
* Update step_action_local.go
* Enable Tests for DRYRUN
* Update runner_test.go
* Update runner_test.go
* Move DRYRUN Test in it's own function
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* feat: handle context cancelation during docker exec
To allow interrupting docker exec (which could be long running)
we process the log output in a go routine and handle
context cancelation as well as command result.
In case of context cancelation a CTRL+C is written into the docker
container. This should be enough to terminate the running
command.
To make sure we do not get stuck during cleanup, we do
set the cleanup contexts with a timeout of 5 minutes
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Philipp Hinrichsen <[email protected]>
* feat: handle SIGTERM signal and abort run
* test: on context cancel, abort running command
This test makes sure that whenever the act Context was canceled, the
currently running docker exec is sent a 0x03 (ctrl+c).
Co-authored-by: Björn Brauer <[email protected]>
* test: make sure the exec funcction handles command exit code
This test makes sure that the exec function does handle
docker command error results
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Philipp Hinrichsen <[email protected]>
Co-authored-by: Björn Brauer <[email protected]>
* feat: add post step to actions and add state command
This commit includes requried changes for running post steps
for local and remote actions.
This allows general cleanup work to be done after executing
an action.
Communication is allowed between this steps, by using the
action state.
* feat: collect pre and post steps for composite actions
* refactor: move composite action logic into own file
* refactor: restructure composite handling
* feat: run composite post steps during post step lifecycle
* refactor: remove duplicate log output
* feat: run all composite post actions in a step
Since composite actions could have multiple pre/post steps inside,
we need to run all of them in a single top-level pre/post step.
This PR includes a test case for this and the correct order of steps
to be executed.
* refactor: remove unused lines of code
* refactor: simplify test expression
* fix: use composite job logger
* fix: make step output more readable
* fix: enforce running all post executor
To make sure every post executor/step is executed, it is chained
with it's own Finally executor.
* fix: do not run post step if no step result is available
Having no step result means we do not run any step (neither pre
nor main) and we do not need to run post.
* fix: setup defaults
If no pre-if or post-if is given, it should default to 'always()'.
This could be set even if there is no pre or post step.
In fact this is required for composite actions and included post
steps to run.
* fix: output step related if expression
* test: update expectation
* feat: run pre step from actions (#1110)
This PR implements running pre steps for remote actions.
This includes remote actions using inside local composite actions.
* fix: set correct expr default status checks
For post-if conditions the default status check should be
always(), while for all other if expression the default status
check is success()
References:
https://docs.github.com/en/actions/learn-github-actions/expressions#status-check-functionshttps://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runspost-if
* fix: remove code added during rebase
If a step does not have either a `run` or `uses` directive it is
considered invalid.
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* refactor: remove composite action runcontext workaround
The RunContext is cloned to execute a composite action with all its
steps in a similar context. This required some workaround, since
the command handler has kept a reference to the original RunContext.
This is solved now, by replacing the docker LogWriter with a proper
scoped LogWriter.
This prepares for a simpler setup of composite actions to be able
to create and re-create the composite RunContext for pre/main/post
action steps.
* test: check env-vars for local js and docker actions
* test: test remote docker and js actions
* fix: merge github context into env when read and setup
* refacotr: simplify composite context setup
* test: use a map matcher to test input setup
* fix: restore composite log output
Since we create a new line writer, we need to log the raw_output as well.
Otherwise no output will be available from the log-writer
* fix: add RunContext JobName to fill GITHUB_JOBNAME
* test: use nektos/act-test-actions
* fix: allow masking values in composite actions
To allow masking of values from composite actions, we need
to use a custom job logger with a reference to the masked
values for the composite run context.
* refactor: keep existing logger for composite actions
To not introduce another new logger while still be able to use
the masking from the composite action, we add the masks to
the go context. To leverage that context, we also add the context
to the log entries where the valueMasker then could get the actual
mask values.
With this way to 'inject' the masked values into the logger, we do
- keep the logger
- keep the coloring
- stay away from inconsistencies due to parallel jobs
* fix: re-add removed color increase
This one should have never removed :-)
* fix: add missing ExtraPath attribute
* fix: merge run context env into composite run context env
This adds a test and fix for the parent environment. It should be
inherited by the composite environment.
* test: add missing test case
* fix: store github token next to secrets
We must not expose the secrets to composite actions, but the
`github.token` is available inside composite actions.
To provide this we store the token in the config and create it in
the GithubContext from there.
The token can be used with `github.token` but is not available as
`secrets.GITHUB_TOKEN`.
This implements the same behavior as on GitHub.
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Marcus Noll <[email protected]>
* fixup! fix: allow masking values in composite actions
* style: use tabs instead of spaces to fix linter errors
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Marcus Noll <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
To make the test logs more readable, we should fold the
output per test into a group.
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This prevented JetBrains GoLand from recognising subtests and treated
it all as single big test, VSCode seems to be still broken in that regard
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Casey Lee <[email protected]>
Prevents random failing of all other runs when qemu binfmt
is not set up
Signed-off-by: Ryan <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* don't merge env var if it exists already
* remove test workflow
* add some tests
* change mergeMaps order instead of checking for existence
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* fix: copy ignored tracked files
* fix
* refactor: Extract callback to fileCollector
fix: temporary tar archive not deletable on windows
fix: `.*` in gitignore ignores all files
* Move nolint: gocyclo
* pass context as parameter
* goimport
* Add fs interface + one test
* fix lint, also test for ignored non tracked file
* fix filename
* Apply suggestions from code review
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* deps: bump go.mod to go1.18
* deps: go mod tidy
* docs: update Go version requirement
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This change reduces the interfaces by removing
obsolete parameters from functions.
Obsolete parameters does not means unused ones, but
parameters which could be retrieved from other parameters
instead.
This should simplify logic and maintainability for these
functions.
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* refactor: split step_context into separate files
This commit moves functions from the step_context.go file into different
files, but does otherwise not change anything.
This is done to make it easier to review the changes made to these
functions in the next commit, where we introduce a step factory to
facilitate better unit testing of steps.
Co-authored-by: Marcus Noll <[email protected]>
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Robert Kowalski <[email protected]>
Co-authored-by: Philipp Hinrichsen <[email protected]>
Co-authored-by: Jonas Holland <[email protected]>
* refactor: introduce step factory and make steps testable
With this commit we're introducing the `stepFactory` and interfaces
and implementations for each different kind of step (run, docker,
local and remote actions).
Separating each step kind into its own interface and implementation
makes it easier to reason about and to change behaviour of the step.
By introducing interfaces we enable better unit testability as now
each step implementation, the step factory and the job executor can
be tested on their own by mocking out parts that are irrelevant.
This commits prepares us for implementing pre/post actions in a
later PR.
Co-authored-by: Marcus Noll <[email protected]>
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Robert Kowalski <[email protected]>
Co-authored-by: Philipp Hinrichsen <[email protected]>
Co-authored-by: Jonas Holland <[email protected]>
* fix: run post steps in reverse order
* test: add missing asserts for mocks
* refactor: use local reference instead of function
This may make code more easy to follow.
* refactor: correct typo in function name
* test: use named structs
* test: only expected valid calls
There are mocks which are only called on certain conditions.
* refactor: use step-model to get step name
Using the step-logger we have to get the logger name from the
step model.
* test: only mock stopContainer if required
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Marcus Noll <[email protected]>
Co-authored-by: Robert Kowalski <[email protected]>
Co-authored-by: Philipp Hinrichsen <[email protected]>
Co-authored-by: Jonas Holland <[email protected]>
Co-authored-by: Casey Lee <[email protected]>
Co-authored-by: Christopher Homberger <[email protected]>
* Fix: panic: reflect: slice index out of range
* Update interpreter.go
* [no ci] Return null for negative indexes
* Add tests for index access
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
act has a feature that skips the checkout action to do a remote
checkout when a local checkout exists. in some cases, e.g. when
running act in a CI, you always want to clone the repository.
The function `WithTestContext` is never called in the project, and
there is just one usage of `TestContext`, inside one of our loggers
Co-authored-by: Casey Lee <[email protected]>
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* feat: add step name to logger field
This change does add the step name to the logger fields. This does
not change the output for our users, but for the json logger, it
does make each step output traceable.
* fix: remove new logger
Since logrus and context both are immutable for our case, we
can just add a new field and store the logger in the context.
Co-authored-by: Casey Lee <[email protected]>
Co-authored-by: Björn Brauer <[email protected]>
* feat: add json logger output
This will allow to format log output as json.
This is helpful in cases where act is not executed on a 'local' machine.
* refactor: use runner config
Using the runner config to configure logging is cleaner.
Co-authored-by: Casey Lee <[email protected]>
this commit adds support for the `::add-mask::` command, which was
implemented as a stub before.
it does not cover debug output that appears when you run act in
verbose mode
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Markus Wolf <[email protected]>
* feat: run jobs in parallel
This changes fixes and restructures the parallel execution of jobs.
The previous changes limiting the parallel execution did break this
and allowed only one job in parallel.
While we run #CPU jobs in parallel now, the jobs added per job-matrix
add to this. So we might over-commit to the capacity, but at least
it is limited.
* fix: correctly build job pipeline
The job pipeline should just append all required pipeline steps.
The parallelism will be handled by the ParallelExecutor and we
shouldn't handle it during building the pipelines.
Also this adds a test, that the ParallelExecutor does run
a limited amount of parallel goroutines.
* test: correct test implementation
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-02-25 18:47:16 +00:00
Björn BrauerGitHubmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* refactor: remove debug error output
Errors should always be logged with an error level and not debug level.
Since the error is returned here, it will be logged later as an error.
Presumably this was a leftover from debugging the executor chain in:
PR: #971
* refactor: debug log wich expression is going to be evaluated
* fix: handle nil in EvalBool
We've seen this issue when the env map is not set-up properly,
i.e. when the env map is nil, EvalBool might return nil, which should
be handled as a falsy value.
* fix: fail on error in if expression and return the evaluation error
Stop running the workflow in case an expression cannot be evaluated.
Fixes: #1008
* fix: remove quotes from inside expression syntax in test
It looks like having an expression inside double quotes inside the
expression syntax is not valid: https://github.com/ZauberNerd/act-test/actions/runs/1881986429
The workflow is not valid. .github/workflows/test.yml (Line: 10, Col: 13): Unexpected symbol: '"endsWith'. Located at position 1 within expression: "endsWith('Hello world', 'ld')"
* refactor: export IsTruthy function from exprparser package
* refactor: use IsTruthy function in EvalBool
* refactor: move debug log for expression rewrite to rewrite function
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* refactor: default empty `if:` statement to `if: success()` in evaluator
Previously the code to default an empty `if:` statement in the yaml file
was implemented in different files in the model package.
Now an empty `if:` statement defaults to `success()` in the expression
evaluator.
* refactor: remove obsolete `FixIfStatement` functions
The introduction of the expression evaluator seems to have made these
functions obsolete, as the test case `TestRunEvent/issue-598` works even
without these functions.
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This change extracts the functionality of reading an `action.y(a)ml` or
creation of a `(Synthetic Action)` into its own type to enable better
unit testing / mocking of those IO operations.
This is done in preparation for the implementation of pre/post action
support in act.
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: Markus Wolf <[email protected]>
This splits the executor from the RunContext into its own function
called newJobExecutor.
We defined an interface called jobInfo which is implemented by the RunContext.
This enables better unit testing because only a small interface needs to
be mocked.
This is a preparation for implementing pre and post actions.
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Marcus Noll <[email protected]>
Co-authored-by: Jonas Holland <[email protected]>
Co-authored-by: Robert Kowalski <[email protected]>
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Marcus Noll <[email protected]>
Co-authored-by: Jonas Holland <[email protected]>
Co-authored-by: Robert Kowalski <[email protected]>
Co-authored-by: Markus Wolf <[email protected]>
* docs: Expand the GITHUB_TOKEN section
* docs: Add a note on leaking GITHUB_TOKEN through shell history
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Github sets the outcome and conclusion to skipped
and this PR does the same during the pipeline run for
the StepResults in act.
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Björn Brauer <[email protected]>
With this change `act` will try to populate the `githubContext.ref` and
`githubContext.sha` with values read from the event payload.
Caveats:
- `page_build` should not have a ref
- `status` should not have a ref
- `registry_package` should set the ref to the branch/tag but the
payload isn't documented
- `workflow_call` should set ref to the same value as its caller but the
payload isn't documented
- most of the events should set the sha to the last commit on the ref
but unfortunately the sha is not always included in the payload,
therefore we use the sha from the local git checkout
Co-Authored-By: Philipp Hinrichsen <[email protected]>
Co-authored-by: Philipp Hinrichsen <[email protected]>
* feat: implement expression evaluator
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: Philipp Hinrichsen <[email protected]>
* feat: integrate exprparser into act
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: Philipp Hinrichsen <[email protected]>
* Escape { and }, do not fail on missing properties
* Fix empty inputs context
* fix: contains() comparison for complex values
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: Philipp Hinrichsen <[email protected]>
Co-authored-by: Christopher Homberger <[email protected]>
This commit moves the githubContext, jobContext and stepResult structs
from the runner package to the model package in preparation for #908
because the expression.go file lives in the runner package and would
introduce cyclic dependencies with the exprparser package.
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Feature: uses in composite
* Negate logic
* Reduce complexity
* Update step_context.go
* Update step_context.go
* Update step_context.go
* Fix syntax error in test
* Bump
* Disable usage of actions/setup-node@v2
* Bump
* Fix step id collision
* Fix output command workaround
* Make secrets context inaccessible in composite
* Fix order after adding a workaround (needs tests)
Fixes https://github.com/nektos/act/pull/793#issuecomment-922329838
* Evaluate env before passing one step deeper
If env would contain any inputs, steps ctx or secrets there was undefined behaviour
* [no ci] prepare secret test
* Initial test pass inputs as env
* Fix syntax error
* extend test also for direct invoke
* Fix passing provided env as composite output
* Fix syntax error
* toUpper 'no such secret', act has a bug
* fix indent
* Fix env outputs in composite
* Test env outputs of composite
* Fix inputs not defined in docker actions
* Fix interpolate args input of docker actions
* Fix lint
* AllowCompositeIf now defaults to true
see https://github.com/actions/runner/releases/tag/v2.284.0
* Fix lint
* Fix env of docker action.yml
* Test calling a local docker action from composite
With input context hirachy
* local-action-dockerfile Test pass on action/runner
It seems action/runner ignores overrides of args,
if the target docker action has the args property set.
* Fix exec permissions of docker-local-noargs
* Revert getStepsContext change
* fix: handle composite action on error and continue
This change is a follow up of https://github.com/nektos/act/pull/840
and integrates with https://github.com/nektos/act/pull/793
There are two things included here:
- The default value for a step.if in an action need to be 'success()'
- We need to hand the error from a composite action back to the
calling executor
Co-authored-by: Björn Brauer <[email protected]>
* Patch inputs can be bool, float64 and string
for workflow_call
Also inputs is now always defined, but may be null
* Simplify cherry-picked commit
* Minor style adjustments
* Remove chmod +x from tests
now fails on windows like before
* Fix GITHUB_ACTION_PATH some action env vars
Fixes GITHUB_ACTION_REPOSITORY, GITHUB_ACTION_REF.
* Add comment to CompositeRestrictions
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Ryan <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* fix: rework `setupShellCommand`
* move all logic to separate function so we can test that later
* split `step.Shell` and `step.WorkingDirectory` setup into own funcs
* general cleanup of function
* use `ActPath` to not collide with checked out repository
* use `shellquote.Split()` instead of `strings.Fields()` for better command split
* replace single string concat with `fmt`
Signed-off-by: hackercat <[email protected]>
* lint(editorconfig): ignore *_test.go due to mixed style
Signed-off-by: hackercat <[email protected]>
* fix: continue jobs + steps after failure
To allow proper if expression handling on jobs and steps (like always,
success, failure, ...) we need to continue running all executors in
the prepared chain.
To keep the error handling intact we add an occurred error to the
go context and handle it later in the pipeline/chain.
Also we add the job result to the needs context to give expressions
access to it.
The needs object, failure and success functions are split between
run context (on jobs) and step context.
Closes#442
Co-authored-by: Björn Brauer <[email protected]>
* style: correct linter warnings
Co-authored-by: Björn Brauer <[email protected]>
* fix: job if value defaults to success()
As described in the documentation, a default value of "success()" is
applied when no "if" value is present on the job.
https://docs.github.com/en/actions/learn-github-actions/expressions#job-status-check-functions
Co-authored-by: Markus Wolf <[email protected]>
* fix: check job needs recursively
Ensure job result includes results of previous jobs
Co-authored-by: Markus Wolf <[email protected]>
* test: add runner test for job status check functions
Co-authored-by: Markus Wolf <[email protected]>
* test: add unit tests for run context if evaluation
Co-authored-by: Björn Brauer <[email protected]>
* refactor: move if expression evaluation
Move if expression evaluation into own function (step context) to
better support unit testing.
Co-authored-by: Björn Brauer <[email protected]>
* test: add unit tests for step context if evaluation
Co-authored-by: Markus Wolf <[email protected]>
* fix: handle job error more resilient
The job error is not stored in a context map instead of a context
added value.
Since context values are immutable an added value requires to keep
the new context in all cases. This is fragile since it might slip
unnoticed to other parts of the code.
Storing the error of a job in the context map will make it more stable,
since the map is always there and the context of the pipeline is stable
for the whole run.
* feat: steps should use a default if expression of success()
* test: add integration test for if-expressions
* chore: disable editorconfig-checker for yaml multiline string
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Björn Brauer <[email protected]>
* feat: read docker credentials from local docker config
* fix: url.Parse requires protocol
Co-authored-by: Björn Brauer <[email protected]>
* fix: docker decides by the existence of . or : if...
... the image is in a custom registry or not.
Co-authored-by: Björn Brauer <[email protected]>
* fix: make docker hostname detection more robust
* test: mock docker config for getImagePullOptions test
By default github actions have a docker config set with a token to pull
images from docker hub.
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: Markus Wolf <[email protected]>
We should reuse an existing context logger if in test context.
This will allow test to setup act with a null logger to assert
log messages.
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: Markus Wolf <[email protected]>
Adds option to rebuild local action docker images
Fixed up README due to missing flags after
PR #714 and #716
Signed-off-by: hackercat <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Don't interpolate joboutputs, before job is donei
All credits go to @ChristopherHX which fixed this issue.
I only created a PR for this so it will be fixed in the upstream binary.
This fixes#758
* Added output test
* Fix typo
* tests(pkg/common) add failing test case for SSH git URL
* fix(pkg/common) github SSH regex for missing .git extension
Co-authored-by: Ryan <[email protected]>
We should reuse an existing context logger if available.
This will allow test to setup act with a null logger to assert
log messages.
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Björn Brauer <[email protected]>
* ci(golangci-lint): switch to latest
remove schema override since it was fixed in upstream
Signed-off-by: hackercat <[email protected]>
* fix: apply lint fixes
* Add asset server and upload handling of binary files
Co-authored-by: Björn Brauer <[email protected]>
* Add asset download parts to the asset server
Co-authored-by: Björn Brauer <[email protected]>
* Add artifact-server-path flag
If the flag is not defined, the artifact server isn't started.
This includes the configuration of ACTIONS_RUNTIME_URL and
ACTIONS_RUNTIME_TOKEN which are set if the server is started.
Co-authored-by: Björn Brauer <[email protected]>
* Move ACTIONS_RUNTIME_* vars into the withGithubEnv setup
Co-authored-by: Björn Brauer <[email protected]>
* feat: add artifact server port as flag
This commits adds a flag to define the artifact server port.
If not given, the port defaults to 34567.
Co-authored-by: Björn Brauer <[email protected]>
* test: add artifact server tests
Co-authored-by: Björn Brauer <[email protected]>
* refactor: use fs.FS
This allows to add tests with in-memory file system
* feat: add support for gzip encoded uploads
Co-authored-by: Björn Brauer <[email protected]>
* test: add artifact integration test
* chore: run act tests with asset server path
Co-authored-by: Björn Brauer <[email protected]>
* docs: add new cli flags
Co-authored-by: Björn Brauer <[email protected]>
* test: add test workflow to testdata
* feat: add log output
* refactor: log shutdown error instead of panic
* feat: use outbound ip for the asset server
This change should allow to use the host ip in macos and windows.
Since docker is running in an intermediate vm, localhost is not
sufficient to have the artifacts in the host system.
* fix: do not use canceled context
To shutdown artifact server, we should not use the already canceled
context but the parent context instead.
Co-authored-by: Björn Brauer <[email protected]>
* feat: shutdown artifact server at end of pipeline
When the pipeline is done the asset server should be shut down
gracefully.
Co-authored-by: Björn Brauer <[email protected]>
* fix: close server if graceful shutdown failed
Co-authored-by: Björn Brauer <[email protected]>
* fix: ignore server closed error from listen call
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Björn Brauer <[email protected]>
* fix: set default shell for containers to sh
This matches GitHub Actions behaviour where,
runners use bash since it's guaranteed to exist there
while containers use sh for compatibility
* tests: fixup for default shell
Running Docker on non-Linux platforms is very painful,
especially in CI. It's better to remove macOS for now,
until a better implementation is present.
Signed-off-by: hackercat <[email protected]>
* ci: unpin goreleaser version
homebrew issue has been fixed
* ci: drop homebrew from goreleaser
act has been for quite a long time in homebrew-core
and it supports all macos/linux so there is no
reason for us to have separate tap, as it might
confuse some users
Co-authored-by: Casey Lee <[email protected]>
* fix: rewrite StepContext env
step env from workflow should be last one to be written
fixes https://github.com/nektos/act/issues/777
* test: verify env correctness
* fix: move test into existing workflow
* Cache dockerhub images from linux for macOS
An attempt to avoid hitting the dockerhub Pull Rate Limit.
* parallelize / refactor docker cache
Co-authored-by: Casey Lee <[email protected]>
* ci: bump Go version & move it to env
* go.mod: bump golang.org/x/term
* ci: capture Windows arm64 binary
Co-authored-by: Casey Lee <[email protected]>
* fix: align other Docker executors to print action
* fix: formatting
* fix: add proper workdir support
* fix: replace script filepath after slice creation
* fix: match substring so it works for pwsh
+ rename containerPath to scriptPath to reflect what value it contains
* Fix local Docker actions: invalid reference format
* Create Dockerfile
* Create Dockerfile
* Update push.yml
* Revert "Create Dockerfile"
This reverts commit 9d1dbbc2c7fdf52963e55feb4148ac2b14d639d0.
* Fix lint
* fix: re-use image that is already present in repo
ubuntu:latest would pull unnecessary blobs from registry and count toward API limit
* fix: revert change
reverting due to issue in image deletion that will have to be investigated separately
Co-authored-by: Ryan <[email protected]>
* docs: fix wrong licence link
* fix: add more formatting settings, reformat all
feat: add vscode extensions recommendations
* docs: update contributing documentation
* fix: remove unnecessary comment
it was due to broken checkout
* docs: rework installation section
it has been brought up few times, new style should be easier
to understand
* docs: update images provided by catthehacker
* docs: replace 18.04 with recent image
* fix: add final new line
* fix[workflow]: multiple fixes for workflow/matrix
fix[workflow]: default `max-parallel`
fix[workflow]: default `fail-fast`, it's `true`, not `false`
fix[workflow]: skipping over the job when `strategy:` is defined but `matrix:` isn't (fixes#625)
fix[workflow]: skip non-existing includes keys and hard fail on non-existing excludes keys
fix[workflow]: simplify Matrix decode (because I "think" I know how `yaml` works) (fixes#760)
fix[tests]: add test for planner and runner
* fix(workflow): use yaml node for env key
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Fix defaults (composite)
* uses-composite: rely on defaults to pass
* Add test_input_required back, needs more tests
* Update Tests to test defaults carefully
* Add outputs field to job model
* Add output interpolation for jobs
* Add otto config reference for interpolated job output values into 'needs' context
* Add output interpolation call after job has completed.
* gofmt
* Remove whitespace
* goimports
Co-authored-by: Casey Lee <[email protected]>
* fix: typo
* fix: remove debug for git references
it generates a massive amount of logs
* feat: read values from env
allows to test act on GHA when it's not a main repo
* fix: merge extrapath with PATH
* fix(tests): add additional shells for testing
* fix(image): update images
pin node to major version only, current node version: 12.22.1
replace most images with `node:12-buster-slim` to prevent
errors on macOS runner due to DockerHub pull limit
replace ocaml image
Co-authored-by: Casey Lee <[email protected]>
* Test fixups for Windows/Forks
* revert change for repo/actor/owner
this is fixed via https://github.com/nektos/act/pull/723
Co-authored-by: Ryan (hackercat) <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Skips docker cp for local actions and use their correct path
Defines GITHUB_ACTION_PATH also for nodejs actions
Evaluate Env of composite action
Evaluate Run of composite action correctly
Evaluate Shell of run step
Evaluate WorkingDirectory of run step
Changed tests for behavior change
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2021-06-10 15:28:23 +00:00
Joseph BendenGitHubmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This patch adds a new command-line flag (`--rm`) to automatically
stop workflow container(s), just prior to exit. The default
behavior is kept, e.g.: the containers continue running at exit.
Fixes: #694
Signed-off-by: Joseph Benden <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* fix: change `env` to be an interface
allows to use GitHub functions like `fromJson()`
* fix: change matrix to an interface instead of map
This allows to use GitHub functions like `fromJson()` to create dynamic
matrixes
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* fix: update issue template
add additional information to be provided by user that should help in troubleshooting
* fix: move markdownlint config and link it
done it so the markdownlint-cli stops shouting about issues
* feat: add Matrix link
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This patch adds two new command-line flags to specify one or
more kernel capabilities to add or remove from the workflow
containers.
The command-line flag `--container-cap-add` allows for adding
specific capabilities on the workflow containers; where as,
The command-line flag `--container-cap-drop` allows for removing
specific capabilities on the workflow containers.
This was developed to specifically be able to add `SYS_PTRACE`
to a workflow I maintain. It involves using this capability to
monitor a make build, to then build a compilation database.
Signed-off-by: Joseph Benden <[email protected]>
* fix: format `.goreleaser.yml`
* feat: add `arm64`/`arm` builds
* feat: capture all binaries built by goreleaser
`upload-artifact` actions will run only on GitHub Actions due to
`ACTIONS_RUNTIME_TOKEN` requirement
* fix: remove `windows/arm/6`
* refactor: remove `gotest.tools`
* remove all references to `gotest.tools` and replace it with
`github.com/stretchr/testify` which was originally used for tests
* bump `golangci-lint` version
* add `depguard` and `importas` to prevent import of unwanted packages
* add custom schema and information about config since
schemastore.org has broken schema for `golangci-lint` config
* fix: handle more error cases
2021-05-17 23:14:49 -07:00
Oliver KoppGitHubmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* fix: hide masked value
The ::add-mask:: command output logs the value to be masked.
This does expose critical information which should be hidden from
the output.
* Add test to not output secret in add-mask command
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Fix: Skip Checkout Regression
* Fix test name
* Add newline to end of checkout push test
* Restore .gitignore from node12 test. Todo: Restore that file after the test
* Format workflow
* Test: Fix path slash direction for windows relative source path
* Add explicit nil test for newRemoteAction
* Regress .secrets during tests
Co-authored-by: Ryan (hackercat) <[email protected]>
* define GITHUB_ACTION_PATH #603
* Add more environment variables
* Add job name
Note: the job name gets a suffix for matrix builds, but this is not part of the env var
* fix: remove unnecessary variables
* feat: add `RepositoryOwner`
credit: @KnisterPeter
* feat: add test for `getGithubContext()`
Co-authored-by: Ryan (hackercat) <[email protected]>
* Regression in the .golangci.yml file
* This looks like an even better fix to #451
The previous solution only prevented the `starting container process caused "exec: \"bash\"`
error when someone added an "extra" path in the workflow using `::add-path`
* Add support for >> $GITHUB_PATH
* The newRunCommand has too high cyclomatic complexity
* Add "linux/arm64" to new test
* The cyclop linter was complaining so I extracted some funcs
* Close some readers
* Fix typo
* fix: add missing composite function
* Fix regress from merging
* Keep the error messages as is
* consolidate with master
* Close the tar reader on defer
* New way to get ContainerWorkdir
* Remove arch from runner test
* Separate the UpdateFromEnv and UpdateFromPath
Co-authored-by: hackercat <[email protected]>
* fix: environment variables sourcing from `/etc/environment`
* fix: move `envs.txt` & `event.json` to `/tmp/`
Since #635 `envs.txt` is not copying properly when running `act` in WSL2
Moving it to fixed location resolves that.
* Potentially solved merge conflict
* Update pkg/model/planner.go
Based on feedback
Co-authored-by: Ryan (hackercat) <[email protected]>
* Fixed compilation error
* added missed test
* Fixed spelling error to work with lint error
Co-authored-by: Ryan (hackercat) <[email protected]>
Co-authored-by: Casey Lee <[email protected]>
* Add option to specify custom GitHub instance
* Use correct GHE API endpoint URLs
Co-authored-by: Markus Wolf <[email protected]>
* Extract slug from GitHub Enterprise URLs
Co-authored-by: Markus Wolf <[email protected]>
* Use GITHUB_TOKEN for clone authenticate if provided
This change will allow use authentication for cloning actions
from private repositories or github enterprise instances.
Co-Authored-By: Markus Wolf <[email protected]>
* Add section about using act on GitHub Enterprise to README
Co-authored-by: Markus Wolf <[email protected]>
* Set GitHubInstance in runnerConfig in runner_test
Co-authored-by: Markus Wolf <[email protected]>
Co-authored-by: hackercat <[email protected]>
Co-authored-by: Markus Wolf <[email protected]>
* Add custom docker registry authentication
Uses DOCKER_USERNAME and DOCKER_PASSWORD as secrets provided into
the act cli.
Closes#527
Co-authored-by: Björn Brauer <[email protected]>
* Add test to check if pull authentication is filled in
* Update debug message to be more descriptive
Co-authored-by: Ryan (hackercat) <[email protected]>
Co-authored-by: Björn Brauer <[email protected]>
Co-authored-by: Ryan (hackercat) <[email protected]>
* Remove pwsh -login and add Pwsh test
* Add Shell Command Test for coverage
* 🧪 Move PWSH Platform definition to inline test, it can always be expanded out later
Reference: https://github.com/nektos/act/pull/660#discussion_r626171728
* Test MacOS Build for transient failure
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* feat: bump `golangci-lint`, add `super-linter`, replace outdated linter
Bump `golangci-lint` version.
Add `super-linter` to lint other languages.
Go linter is disabled because it's currently broken:
https://github.com/github/super-linter/pull/370
Replacing `scopelint` with `exportloopref`: "[runner] The linter
'scopelint' is deprecated (since v1.39.0) due to: The repository of the
linter has been deprecated by the owner. Replaced by exportloopref."
Fixed formatting in `.golangci.yml`
Add addtional linters:
`misspell`: purely style, detects typos in comments
`whitespace`: detects leading and trailing whitespace
`goimports`: it's gofmt + checks unused imports
* fix: lint/fix `go` files
* fix: lint with `standardjs`
* fix: lint/fix with `markdownlint`, make template more verbose
* feat: add lint stuff to makefile
* fix: `UseGitIgnore` formatting
* fix: lint/fix `README.md`
Co-authored-by: Casey Lee <[email protected]>
feat: add flag `--no-recurse` to disable recursion when reading workflows from directories
feat: added more tests to `TestPlanner`, renamed `TestJobFileInfo` to more appropriate name `WorkflowPlanTest`
style: changed error message to lowercase, added single quotes for better visibility
Co-authored-by: Casey Lee <[email protected]>
Walking the directory tree underneath `.git/refs` is not reliable, as it usually does not
return tags, especially for freshly cloned repos and/or tags fetched from a remote.
The go-git library provides an iterator over all git references.
This approach prefers a reference (tag, branch) from go-git, if found. If none is found,
it falls back to the previous implementation.
- Don't set architecture, let Docker host decide it's own platform,
remove `runtime` dependency and don't show default in `--help`
- Remove most tests, we need to check only once if it works on
different platform
- Rename `DeleteImage` to `RemoveImage` to conform to existing
function in `docker` cli, added options to specify `force` and
`pruneChildren`
* refactor: move survey/cfgs into own funcs, read XDG base dirs for config
Moved actrc locations to own func since it can be reused.
Moved survey part to own func due to high cyclomatic complexity.
Added XDG config dir to config locations.
Replaced using HOME envvar with go-homedir module since it's already
an indirect dependency and handles home directories better
for each GOOS.
* refactor: use `go-homedir`, check if XDG var is not empty
* Add support for composite actions
* Fix to make more complex composite actions work
* Fix to make more complex composite actions work
* Let's validate the steps in the composite steps to fail on uses and run's without shell, like the real world
* Add support for composite actions
* Add workflow to test composite actions
* Log instead of panicing when output is mismatched
* Merge maps so environment variables are not lost
* Remove Debug
* Correect merge error
* Remove invalid composite tests.
* Fix composite test
Co-authored-by: Casey Lee <[email protected]>
Co-authored-by: monkers <[email protected]>
Co-authored-by: Mike Moncrieffe <[email protected]>
* Comment for public function
* Add git describe fallback
* spelling: github
* Set initial branch to satisfy tests for modern git
* Clarify -even- if
* Go 1.16
* Support running commands in repositories without action.yaml
Support runnings commands with only a Docker file
Co-authored-by: Casey Lee <[email protected]>
Commit af5140f13e introduced support for
specifying a container image platform for cross-platform image building.
Unfortunately, attempting to execute a docker command that includes the
`--platform` flag against Docker daemons using API Version 1.40 and
before results in the following error:
```
"specify container image platform" requires API version 1.41, but the Docker daemon API version is 1.40
```
To allow `act` to be used on the 19.03 Docker CE and earlier versions,
this patch simply checks the Docker daemon API version and only
specifies platform specification when the daemon API version is 1.41 or
greater.
Fixes Issue #586
* feat: add code testing on macos
* change name of test job
Keep the test job named `test` to align with current branch protection checks
Co-authored-by: Casey Lee <[email protected]>
* Add QEMU to run different architectures
* Update dependencies in `go.mod`
* Add `--container-architecture` flag to specify custom image architecture
Co-authored-by: Casey Lee <[email protected]>
* fix: give case insensitive secret more meanigful name
* refactor: use `string` in generating `env:` and `steps:` for workflows
Smaller text generation is much better to read with normal strings than
raw string literals.
* feat: sort keys for `env:` so it's always in specific order
* fix: update test workflows
* fix: replace `\` with `/` in git ref to fix `git_test.go` on windows
Paths on Windows use backslash (`\`) as directory separator and this breaks `TestGitFindRef()`.
Replacing `\` with `/` in git ref fixes that issue.
* fix: replace `gopkg.in/godo.v2/glob` with std library `path/filepath`
`github.com/go-godo/godo` lib has been last updated in 2016 and it also
depends on another outdated lib `github.com/MichaelTJones/walk` with
last update in 2016. This also fixes `permission_denied` errors on
Windows (and perhaps Linux in some specific cases). I'm not aware of
any performance improvement or drawback because of that change.
I got an error like this after hitting `act` command.
> Error: Error response from daemon: cannot share the host's network namespace when user namespaces are enabled
According to the document, when user namespaces are enabled on the Docker daemon,
neither host network mode and --privileged work without --userns=host. Since `act`
uses host network mode to match GitHub Actions runners, it cannot run jobs when
user namespaces are enabled. So I added the flag.
https://docs.docker.com/engine/security/userns-remap/#user-namespace-known-limitations
Co-authored-by: Casey Lee <[email protected]>
This fixes#499, where a matrix strategy with only include keys ends up
causing multiple builds. This bugs appears to have been introduced in #415,
when extra include keys are added in the matrix strategy. The cause
seems to be because the CartesianProduct function returns an item with
empty keys, instead of return an empty set.
Co-authored-by: Ed Tan <[email protected]>
* Add survey during first run for a default image
* few minor formatting updates
* Use image from DockerHub
Co-authored-by: Casey Lee <[email protected]>
* Update flags in README
* Add secrets and `.env` files examples
* Fix typo: environment
* Fix typo: returned
* Add --version back, since I've built act without ldflags
* using ubuntu-18.04 instead of ubuntu-latest when specifying platform -P flag in readme, it cause a little confusion
* fix a confusion in .actrc for the image name
This is a solution to issue #416 where environment variables created or
changed in the previous step are not usable in the next step because
the rc.ExprEval is from the beginning of the previous step.
This change refactors setupEnv so that before interpolating the environment
variables a NewExpressionEvaluator is created.
Fixes: 416
If an action uses the branch to pin the major version, `- use: user/action@v1`
will stop with an error: "v1: reference not found."
In this case `act` should use refs/remotes/origin/v1 as a name to resolve v1 revision.
Co-authored-by: Casey Lee <[email protected]>
* Upgrade to the official golangci-lint action and fix some issues it found
* Update deps
* Remove a shadow warning
* Initialize the splitPattern only once
* Initial attempt at supporting $GITHUB_ENV
Needs some polishing and tests
* Now it's actually working
* Replace golang.org/x/crypto/ssh/terminal with golang.org/x/term
* Disable the issue-228 test again
* The linter is picky
* Discovered that the workflow/envs.txt had to exist in certain cases
* Fix small linter issue
* fixes include directive of strategy build.
* Adds test for include and exclude in matrix builds.
* ubuntu-16.04 instead of 20.04
* Adds more platforms for runner_test
Prevent most "skipping unsupported platform messages".
* correct printing for unsupported platform
* fix merge
* Test more if env variants
* The correct negation syntax is !=
* Make the Interpolate function support negated booleans from envs
* Move assert := a.New(t) into t.Run
This uncovered that some of the test premisses was wrong and the
Eval Bool function also had flaws
* Remove a stray logrus import
* Add an ACT env set to true
This can be used to skip certain steps that you don't want to run locally
when testing. E.g. steps that sends messages to Slack channels on successful
builds etc.
* Add a description about env.ACT to the readme
* A new attempt at Interpolation and EvalBool
* One small merge fix
* Remove some fmt.Printfs
* Fix some merge conflicts
* Test more if env variants
* The correct negation syntax is !=
* Make the Interpolate function support negated booleans from envs
* Move assert := a.New(t) into t.Run
This uncovered that some of the test premisses was wrong and the
Eval Bool function also had flaws
* Remove a stray logrus import
* Simpler list view
* lint
* readding graph viz with -g/--graph
Co-authored-by: Leonid Shamis <[email protected]>
Co-authored-by: Casey Lee <[email protected]>
* When running on Windows the correct path separator must be used. filePath.join is OS aware, so when we want to use forward slash use path.join instead.
on windows docker cp should end with \. when copying a directory
when running npm modules we should pass in path with all forward slashes
This fixes#331
* When calculating relative folders on Windows for destination path on Linux, we need to change \ for /
* Reduce complexity by extracting methods
* V1 does not point to a file that does not exist
* Looks like something else is the cause of this test breaking. Last successful build is #371, builds after that are failing
* Rewrite contexts before evaluating them
* Precompile context and expression patterns
* Test trim before rewrite
The current contextPattern is quite constraining and would fail the
rewrite of a context with trailing spaces. Triming happens during the
execution of Interpolate, and these tests aim to detect future breaking
changes on this behavior.
Co-authored-by: Casey Lee <[email protected]>
* Pull latest changes from ref using cached repo
Act switched from checking out a branch to checking out
a hash in order to resolve#24 - see
bc5c23e8e4
This had a side effect of preventing `w.Pull()` from updating
to the latest commit if a ref was a branch/tag and the repo
already exists on disk.
This commit checks if input.Ref is equivalent to the resolved
hash, and runs `r.Fetch()` and checks out the branch for input.Ref
if it is different. `w.Pull()` can then pull the latest changes
before we checkout `*hash` again like normal
* Extract CloneIfRequired to reduce cyclomatic complexity
Co-authored-by: Casey Lee <[email protected]>
Currently setting workflows with the -W (--workflows) flag only accepts
a folder. It is not possible to run a single workflow.yml file.
This commit allows either a single file or a directory to be passed into
the workflows flag.
Previously secrets would be shown in log output as provided. This
commit updates the stepLogFormatter to replace any instance of the secret
string with "***", as GitHub Actions would
Known issues: If the secret is a generic string (such as "docker"), all
occurances of that string will be replaced in the output
Co-authored-by: Casey Lee <[email protected]>
This adds the `-a` flag when running `act` to change the username of the GITHUB_ACTOR environment variable
Co-authored-by: Casey Lee <[email protected]>
* Ignore .idea
* Add Env to the RunContext vm so we can Evaluate and Interpolate `env.xx`
* Make EvalBool support expressions more in line with the github runner
* Turns out Boolean(value) is what github is doing after all
* Add test for github context as well
In #222 I added case normalisation to ReadAction() to ensure that Docker and docker are interpreted the same way.
I realised that this was being done at the wrong level and required multiple type conversions. By implementing `func (a ActionRunsUsing) UnmarshalYAML` we can lowercase the string as it's being unserialized
This has an added benefit that any time this type is hydrated the `runs.using` key will be lowercased, rather than relying on `ReadAction()`
Currently, providing `using: Docker` is an error as the switch statement in `step_context.go` is case sensitive.
Actions run successfully on GitHub with `using: Docker` leading me to believe that they're case insensitive. This commit updates `act` to match
Co-authored-by: Casey Lee <[email protected]>
The string comparison in `step_context.go` is currently case sensitive. This commit adds an error that returns the valid options and tells the user what value they passed
* feat: load every environment from --env-file to workflow
* fix: pass dotenv's environments through by context
* updates to support --secret-file
Co-authored-by: Casey Lee <[email protected]>
again, thought this might be helpful for other users.
**Caveat**: I'm not an InfoSec expert, so someone should look this over.
Don't want to encourage people to do stupid things.
- Refactored logDockerResponse function to remove extra if-else nesting
- logDockerResponse func now returns an error if error was detected from the log stream
- logDockerResponse will check for msg.ErrorDetail.Message and bail if there's an error
- No need to compile regexes in the function, moved them to vars
- Fixed github http regex: it was not matching https://github.com/foo/bar (no git extension)
- Use `make help` to find available development targets
- PR descriptions: minimal, only what and why, no task lists or file listings
- Reference issues and PRs by full URL, not by number
- Use Conventional Commits for commit messages and PR titles, plus the `enhance` type for user-facing enhancements
- Add an `Assisted-by: AGENT_NAME:MODEL_VERSION` trailer to commit messages, never `Co-Authored-By` or `Signed-off-by`
- Attribute agent authorship on one trailing line in issue and pull request comments, never as a PR description section
- Never force-push, amend, or squash unless asked. Use new commits and normal push for pull request updates
- Comments: write almost none, short and preferably same-line, explaining why for a future reader. Never narrate code, the change or the prompt. Preserve existing ones that still apply
- Add the current year into the copyright header of new `.go` files
- Read `DEVELOPMENT.md` for internals and conventions
- Ensure no trailing whitespace in edited files
- Run `make fmt` after `.go` edits, `make tidy` after `go.mod` edits, and `make checks` for the non-lint source checks
- Lint what changed with `make lint-go`, and `make lint-go-windows` for Windows and platform-split files
- Fix the cause rather than disabling a linter or weakening a test. Where unavoidable, use the narrowest scope with a trailing comment giving the reason
- Run single go tests with `go test -run '^TestName$' ./modulepath/`. `make test` self-skips the integration tests without docker or network, `make test-dind` runs the daemon-facing tests against the built dind image
- Write the fewest, fastest tests covering the behavior, extending an existing one where possible. Prefer unit tests where logic is testable in isolation
- Wait on a deterministic condition rather than `sleep`
- Update the files under `docs/` when behavior documented there changes
Mount a volume on `/data` if you want the registration file and optional config to survive container recreation (see [scripts/run.sh](scripts/run.sh)).
> **`/data` does not hold the image cache.** It is the runner's working directory and contains only the `.runner` registration file and, optionally, your config file. Images pulled for jobs live in the *Docker daemon's* data root, which for the `dind` flavours is inside the container (`/var/lib/docker`, or `/home/rootless/.local/share/docker` for `dind-rootless`). To keep the image cache across restarts, give that path its own volume as well — otherwise every new container re-pulls the job images. With the `basic` flavour the images live on whichever daemon you point the runner at, so there is nothing extra to persist.
### Image flavours
The image is published in three flavours, all built from the single multi-stage [Dockerfile](Dockerfile) in this repository. They differ only in how a Docker daemon is made available to the jobs the runner executes; the `gitea-runner` binary inside them is identical.
| Tag | Build target | Base image | Docker daemon | Process supervisor | Runs as |
| --- | --- | --- | --- | --- | --- |
| `latest` (and `<version>`) | `basic` | `alpine` | none — uses an external daemon you provide | [`tini`](https://github.com/krallin/tini) | `root` |
| `latest-dind` | `dind` | `docker:dind` | bundled, started inside the container | [`s6`](https://skarnet.org/software/s6/) | `root` (privileged) |
| `latest-dind-rootless` | `dind-rootless` | `docker:dind-rootless` | bundled, started rootless inside the container | [`s6`](https://skarnet.org/software/s6/) | `rootless` (UID 1000) |
#### `latest` — basic
The default flavour ships only the runner on a minimal Alpine base. It contains **no Docker daemon of its own**: jobs that use `docker://` images need a daemon supplied from outside the container, typically by bind-mounting the host's socket:
```bash
docker run -e GITEA_INSTANCE_URL=https://your_gitea.com -e GITEA_RUNNER_REGISTRATION_TOKEN=<your_token> \
`tini` is the entrypoint (it reaps zombie processes), and it just runs [`scripts/run.sh`](scripts/run.sh), which registers the runner on first start and then execs `gitea-runner daemon`. This flavour does not need `--privileged`. The trade-off is that jobs share the host's daemon, so they can see other containers and images on that daemon.
#### `latest-dind` — Docker-in-Docker
This flavour is based on the official `docker:dind` image and bundles its own Docker daemon, so it needs no external socket — only the `--privileged` flag that Docker-in-Docker requires:
```bash
docker run --privileged -e GITEA_INSTANCE_URL=https://your_gitea.com -e GITEA_RUNNER_REGISTRATION_TOKEN=<your_token> \
--name my_runner gitea/runner:latest-dind
```
Two processes have to run side by side here (the Docker daemon and the runner), so the entrypoint is the [`s6`](https://skarnet.org/software/s6/) supervision tree under [`scripts/s6`](scripts/s6) instead of `tini`. `s6` starts `dockerd`, and the runner service waits for the daemon to come up (`s6-svwait`) before launching [`run.sh`](scripts/run.sh). Each container has a private daemon isolated from the host's, at the cost of running privileged.
Same idea as `dind`, but built on `docker:dind-rootless` so the bundled daemon and the runner run as an unprivileged user (`rootless`, UID 1000) rather than `root`. `DOCKER_HOST` is preset to `unix:///run/user/1000/docker.sock` so the runner talks to the rootless daemon. This reduces the blast radius compared to the privileged `dind` flavour, but rootless Docker carries the usual rootless limitations (networking, cgroups, storage drivers, and some operations that need additional host configuration such as `/etc/subuid` / `/etc/subgid` mappings and unprivileged user-namespace support).
> **The UID is fixed at 1000.** It comes from the `rootless` user baked into the upstream `docker:dind-rootless` base image, and the bundled daemon always listens on `/run/user/1000/docker.sock` inside the container, so running this flavour as a different user (`--user 1001`) does not work. If you need the runner to talk to a *host* rootless daemon that runs under some other UID, use the `basic` flavour instead and bind-mount that daemon's socket (see [examples/vm/rootless-docker.md](examples/vm/rootless-docker.md)); pointing `DOCKER_HOST` at a host socket from inside `dind-rootless` will not work. Changing the UID otherwise means rebuilding the image from a base with a different `rootless` user.
> **Note on Podman:** these images target the Docker daemon. The bundled `dind`/`dind-rootless` daemons are `dockerd`, not Podman, and the `basic` flavour expects a Docker-compatible socket. Running them under rootless Podman is not a supported configuration, though pointing the `basic` flavour at a Podman socket that emulates the Docker API may work for some workloads.
### Configuration
### Configuration
You can also configure the runner with a configuration file.
The runner reads a YAML file. Without one, every option keeps its default.
The configuration file is a YAML file, you can generate a sample configuration file with `./act_runner generate-config`.
```bash
```bash
./act_runner generate-config > config.yaml
./gitea-runner config init # write config.yaml, with no option set
./gitea-runner config generate | less # read what the options do
./gitea-runner -c config.yaml daemon # -c also works on register and cache-server
```
```
You can specify the configuration file path with `-c`/`--config` argument.
`config generate` prints [config.example.yaml](internal/pkg/config/config.example.yaml). Every value in it is commented out, so copy the lines you want to change into your own file and uncomment them.
#### Editing a config file
`config` edits a file in place, which is handy in provisioning scripts:
```bash
```bash
./act_runner -c config.yaml register # register with config file
./gitea-runner configset runner.capacity 4
./act_runner -c config.yaml daemon # run with config file
./gitea-runner configset runner.timeout 90m # written as 1h30m0s
./gitea-runner config set runner.envs.MY_VAR value
You can read the latest version of the configuration file online at [config.example.yaml](internal/pkg/config/config.example.yaml).
A key is its dotted YAML path. An unknown key, a value of the wrong type, or `add`/`remove` on anything but a list is refused before the file is touched. `set` replaces a whole list when you give it several values.
An edit keeps the comments and the key order of the file. Indentation becomes two spaces, and a blank line between two values is dropped.
`config get`, `set`, `add` and `remove` use `config.yaml` (or `config.yml`) from the working directory, then from the directory of the binary, and print their choice to stderr. `config init` writes `config.yaml` in the working directory, and refuses to overwrite an existing config without `--force`. Pass `-c` for another path.
#### Environment variables
Earlier releases let a few environment variables (`GITEA_DEBUG`, `GITEA_TRACE`, `GITEA_RUNNER_CAPACITY`, `GITEA_RUNNER_FILE`, `GITEA_RUNNER_ENVIRON`, `GITEA_RUNNER_ENV_FILE`) override parts of the config. They are gone, use the YAML file for all settings. The Docker images still read their own variables, such as `RUNNER_STATE_FILE`, see [scripts/run.sh](scripts/run.sh) and the container documentation below.
### Labels
Labels decide **which jobs a runner accepts** and **how it runs them**. A job's `runs-on` is matched against the runner's label names; the first match wins and selects the execution environment for that job.
A label is written as:
```text
<name>[:<schema>[:<args>]]
```
| Part | Meaning |
| --- | --- |
| `name` | The name a workflow refers to in `runs-on`, e.g. `ubuntu-latest`. |
| `schema` | Either `docker` or `host`. Defaults to `host` when omitted. |
| `args` | Only used by the `docker` schema: the image to run the job in. |
Two schemas are supported:
- **`docker://<image>`** — the job runs inside a container created from `<image>`:
a workflow with `runs-on: ubuntu-latest` is executed in the `runner-images:ubuntu-latest` container, and one with `runs-on: macos` is executed directly on the host.
Names may themselves contain a colon (for example `pool:e57e18d4-10d4-406f-93bf-60f127221bdd`); only `host` and `docker` are treated as schemas.
If a job's `runs-on` matches none of the runner's labels, the job still runs, in the default `docker.gitea.com/runner-images:ubuntu-latest` image. Images maintained for this purpose are listed at [gitea/runner-images](https://gitea.com/gitea/runner-images).
Labels are chosen at registration time (`--labels`, or the interactive prompt) and can be changed afterwards by editing `runner.labels` in the config file, or in the Gitea UI under the runner's settings.
#### Registration vs config labels
If `runner.labels` is set in the YAML file, those labels are used during `register` and the `--labels` CLI flag is ignored.
The `daemon` command also accepts `--labels` (which defaults to the `GITEA_RUNNER_LABELS` environment variable), so the labels of an already registered runner can be changed without deleting its registration file. The most explicit source wins:
```
--labels / GITEA_RUNNER_LABELS > runner.labels in the config file > labels in the .runner file
```
Whenever the resulting labels differ from the ones in the registration file, they are written back to it and re-declared to the Gitea instance on startup.
> **Note:** A runner that only exposes `host` labels still needs access to a Docker daemon (e.g. a mounted `/var/run/docker.sock`) whenever a job uses a `docker://` action or a service container. `host` labels only change where the job's own steps run; container-based steps and actions are still executed with Docker.
#### Service containers
A job's `services` are started before its steps run. When a service's image or its `options` declare a healthcheck, the runner waits for it to report healthy, so a workflow does not have to poll for its own services:
```yaml
services:
postgres:
image: postgres:17
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-retries 10
```
A service that reports unhealthy fails the job right away, with its container log. One that never becomes healthy fails it after `container.service_ready_timeout` (default `5m`, negative disables the wait). A service that exits without declaring a healthcheck only gets its log and a warning.
A job in a container reaches a service by its id on the job network, on the port the service listens on, for example `psql -h postgres -p 5432`. The started containers also fill the `job` context: `job.container.{id,network}` and `job.services.<id>.{id,network,ports}`, where `ports` maps a container port to the host port Docker published it on, for the services that publish one.
Unlike GitHub, a job whose steps run on the host (a `host` label without `container:`) starts no service containers, so `job.services` and `job.container` stay empty. Give such a job a `container:` when it needs services.
#### Proxy
Set these variables in the runner's environment, with systemd `Environment=`, `docker run -e`, or Kubernetes `env:`:
```sh
http_proxy=http://proxy.example:3128
https_proxy=http://proxy.example:3128
no_proxy=gitea.internal,.example.local
```
The runner uses them for its own requests and gives them to every job, in lower and upper case.
These hosts are added to `no_proxy` for jobs, so they are always reached directly:
- the cache server
- `localhost`, `127.0.0.1` and `::1`
- the job's service containers
- the Docker daemon, when it is reached over `tcp://`
Gitea is not added. Add it to `no_proxy` yourself if it should be reached directly.
To change a value for one job, set it in a step's `env:` or in the job's `container.env`. Setting it at workflow or job level has no effect. To change it for the whole runner, set it in `runner.envs`. A `no_proxy` set there is added to the list above instead of replacing it.
Images are pulled by the Docker daemon, which needs its own proxy setting. In the `dind` images the daemon runs in the same container and reads the variables above. For any other daemon, see [the Docker documentation](https://docs.docker.com/engine/daemon/proxy/). The runner logs a warning at startup if it has a proxy and the daemon does not.
Dockerfile actions are built with these variables as build arguments, so their `RUN` steps can reach the network.
A password in a proxy URL is hidden in job logs. Any step can still read it, because the step is given the proxy URL in its environment.
#### Caching (`actions/cache`)
Each runner starts its own cache server automatically. Cache entries are local to that runner — runners do not share a cache by default.
**Cache service v2**
`actions/cache@v4.2` and later can use the *cache service v2* API. The runner serves it from the same store as v1, on by default, and it works with `external_server`. Turn it off with:
```yaml
cache:
v2: false
```
Those actions refuse any host they do not take for GitHub. Rather than misreport the server URL, the runner edits that check out of the action's own bundle and keeps the untouched copy beside it; a bundle it does not recognise is left alone and keeps to v1. The same edit lets the stock `actions/upload-artifact` and `actions/download-artifact` work from `v4.4.0` on, without the `gitea-upload-artifact` fork.
**Shared cache across multiple runners**
Run one dedicated `gitea-runner cache-server` that all runners point at.
1. Create a config file for the cache server host:
external_secret: "replace-with-a-strong-random-secret" # must match the server
# external_secret_file: /path/to/secret # secret can also be passed via a file
```
Alternatively, mount the same NFS/CIFS share on every runner and point `cache.dir` at it — simpler, but with weaker isolation between repositories.
**S3 / MinIO** — mount object storage as a FUSE filesystem (e.g. [s3fs](https://github.com/s3fs-fuse/s3fs-fuse) or [goofys](https://github.com/kahing/goofys)) and set `cache.dir` to the mount point.
Flags `--dir`, `--host`, and `--port` on `cache-server` override the corresponding `cache.*` YAML keys; all other settings, including `external_secret`, require the config file.
#### Official Docker image
Besides `GITEA_INSTANCE_URL` and `GITEA_RUNNER_REGISTRATION_TOKEN`, the image entrypoint supports optional variables such as `CONFIG_FILE` (passed through as `-c`), `GITEA_RUNNER_LABELS`, `GITEA_RUNNER_EPHEMERAL`, `GITEA_RUNNER_ONCE`, `GITEA_RUNNER_NAME`, `GITEA_MAX_REG_ATTEMPTS`, `RUNNER_STATE_FILE`, and `GITEA_RUNNER_REGISTRATION_TOKEN_FILE`. See [scripts/run.sh](scripts/run.sh) for exact behavior.
For a fuller container-oriented walkthrough, see [examples/docker](examples/docker/README.md).
While the runner is idle it cleans up after earlier jobs:
- when `container.bind_workdir` is enabled, stale task workspace directories older than `runner.workdir_cleanup_age` are removed (default: `24h`; set `0` to disable)
- only purely numeric subdirectories under `container.workdir_parent` are treated as task workspaces and may be removed
- cleanup assumes `container.workdir_parent` is not shared across multiple runners
- on runners that use docker, per-job networks left behind by jobs the runner did not live to tear down are removed, identified by the `com.gitea.runner.uuid` label carrying this runner's uuid
- cleanup runs every `runner.idle_cleanup_interval` (default: `10m`; set `0` to disable), and setting either knob to `0` disables all of the above
#### Post-task script (`runner.post_task_script`)
Optional host script that runs **after** each task's built-in cleanup (post-steps, container teardown, bind-workdir removal). Use it for extra machine housekeeping — Docker pruning, disk cleanup, and similar.
**While the script runs, the runner stops task heartbeats and stays offline from Gitea's perspective until the script exits (or hits `runner.post_task_script_timeout`, default `5m`).** A script that blocks without exiting keeps the runner from taking new work for up to that timeout. Script output goes to the runner log, not the job log; a non-zero exit is warned but does not change the job result.
On Windows, use `.exe`, `.bat`, or `.cmd` paths; **PowerShell (`.ps1`) is not supported yet** as the configured path — wrap commands in a `.cmd` file instead.
See **[docs/post-task-script.md](docs/post-task-script.md)** for lifecycle details, environment variables, timeout interaction, and platform notes.
Optional scripts that run **inside the job environment** (the job container, or the host in host mode), before the job's first step and after its last one. They are the equivalent of GitHub's `ACTIONS_RUNNER_HOOK_JOB_STARTED` / `ACTIONS_RUNNER_HOOK_JOB_COMPLETED`, which are read when the settings are unset.
Because they run where the steps run and see the job's environment, they are the place for per-job setup no workflow should have to carry: registry logins, mirror configuration, or masking runner-wide secrets with `::add-mask::`. Their output is part of the job log and is scanned for workflow commands, and they can export to the job through `$GITHUB_ENV` and `$GITHUB_PATH`.
Both hooks are synchronous and block the job while they run. Either one exiting non-zero fails the job, and there is no per-hook timeout.
See **[docs/job-hooks.md](docs/job-hooks.md)** for the execution order, environment, and platform notes.
// Copyright 2023 The Gitea Authors. All rights reserved.
// Copyright 2023 The nektos/act Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// Package artifactcache provides a cache handler for the runner.
//
// Inspired by https://github.com/sp-ricard-valverde/github-act-cache-server
//
// TODO: Authorization
// TODO: Restrictions for accessing a cache, see https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache
// TODO: Force deleting cache entries, see https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
h.logger.Warnf("artifact service forward to %s: %v",target,err)
w.WriteHeader(http.StatusBadGateway)
},
}
ifcred.InsecureTLS{
proxy.Transport=insecureTransport
}
proxy.ServeHTTP(w,r)
}
// insecureTransport is shared, because a transport per request would pool no connections.
varinsecureTransport=&http.Transport{TLSClientConfig:&tls.Config{InsecureSkipVerify:true}}//nolint:gosec // the runner reaches its instance on the operator's say-so
returnfmt.Errorf("docker has no address pool left for this job's network, lower runner.capacity or widen default-address-pools in the docker daemon config: %w",err)
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
packagecontainer
import(
"context"
"errors"
"testing"
"time"
cerrdefs"github.com/containerd/errdefs"
"github.com/moby/moby/api/types/network"
mobyclient"github.com/moby/moby/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
funcTestIsAddressPoolExhausted(t*testing.T){
assert.True(t,isAddressPoolExhausted(cerrdefs.ErrInvalidArgument.WithMessage("Error response from daemon: all predefined address pools have been fully subnetted")))
assert.True(t,isAddressPoolExhausted(errors.New("could not find an available, non-overlapping IPv4 address pool among the defaults to assign to the network")))
assert.False(t,isAddressPoolExhausted(cerrdefs.ErrInvalidArgument.WithMessage("invalid subnet 10.0.0.0/8: it overlaps with an existing network")))
}
// Of this runner's networks, only the ones nothing is attached to and old enough to predate
// any job now starting are the runner's to reclaim. An unexpected NetworkRemove fails the
// test on its own, since testify has no expectation to match it against.
logger.Errorf("pulling image '%v' (%s) failed with credentials %s retrying without them, please check for stale docker config files",imageRef,input.Platform,err.Error())
imagePullOptions.RegistryAuth=""
err=pullOnce(imagePullOptions)
}
iferr==nil{
returnnil
}
// a registry that is down should not fail a job whose image is already here
assert.NoError(t,err,"Failed to create ImagePullOptions")//nolint:testifylint // pre-existing issue from nektos/act
assert.Equal(t,"",options.RegistryAuth,"RegistryAuth should be empty if no username or password is set")//nolint:testifylint // pre-existing issue from nektos/act
assert.NoError(t,err,"Failed to create ImagePullOptions")//nolint:testifylint // pre-existing issue from nektos/act
assert.Equal(t,"eyJ1c2VybmFtZSI6InVzZXJuYW1lIiwicGFzc3dvcmQiOiJwYXNzd29yZFxuIiwic2VydmVyYWRkcmVzcyI6Imh0dHBzOi8vaW5kZXguZG9ja2VyLmlvL3YxLyJ9",options.RegistryAuth,"RegistryAuth should be taken from local docker config")
}
// A digest-pinned image is immutable, so its local copy is always current.
// Should we early-exit here, since there is no host nor socket to talk to?
returnSocketAndHost{},fmt.Errorf("DOCKER_HOST was not set, couldn't be found in the usual locations, and the container daemon socket ('%s') is invalid",socketHost.Socket)
}
// Default to DOCKER_HOST if set
ifsocketHost.Socket==""&&hasDockerHost{
// Cases: 4A
log.Debugf("Defaulting container socket to DOCKER_HOST")
socketHost.Socket=socketHost.Host
}
// Set sane default socket location if user omitted it
ifsocketHost.Socket==""{
// Cases: 4B
socket,_:=socketLocation()
// socket is empty if it isn't found, so assignment here is at worst a no-op
log.Debugf("Defaulting container socket to default '%s'",socket)
socketHost.Socket=socket
}
// Exit if both the DOCKER_HOST and socket are fulfilled
ifhasDockerHost{
// Cases: 1A, 2A, 3A, 4A
if!isDockerHostURI(socketHost.Socket){
// Cases: 1A, 2A
log.Debugf("DOCKER_HOST is set, but socket is invalid '%s'",socketHost.Socket)
}
returnsocketHost,nil
}
// Set a sane DOCKER_HOST default if we can
ifisDockerHostURI(socketHost.Socket){
// Cases: 3B
log.Debugf("Setting DOCKER_HOST to container socket '%s'",socketHost.Socket)
socketHost.Host=socketHost.Socket
// Both DOCKER_HOST and container socket are valid; short-circuit exit
returnsocketHost,nil
}
// Here there is no DOCKER_HOST _and_ the supplied container socket is not a valid URI (either invalid or a file path)
// Cases: 2B <- but is already handled at the top
// I.e. this path should never be taken
returnSocketAndHost{},fmt.Errorf("no DOCKER_HOST and an invalid container socket '%s'",socketHost.Socket)
common.Logger(ctx).Warnf("could not create the job's process group; processes a step leaves behind can only be reclaimed by the workspace scan: %v",err)
logger.Warnf("host environment cleanup did not finish within %s; continuing job completion, scratch state may be leaked and is reclaimed by the idle stale-dir sweep",hostCleanupTimeout)
// Local actions keep a human-readable, repository-namespaced prefix and gain a short hash suffix that makes the tag unique per (repository, actionName).
// See https://gitea.com/gitea/runner/issues/1039.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.