Give the runner a node before asking it to run an action
ci / build and test (push) Failing after 5s
ci / api image (push) Skipped
ci / android head (push) Failing after 3s

Every job died on its first line: "Cannot find: node in PATH", from actions/checkout.
act_runner executes each `uses:` action with node inside the job container, and the image
this runner is configured with has none — so nothing in the pipeline had run yet, including
the tests the image job gates on.

A `run:` step is shell rather than node, so one placed ahead of the first action can fix the
job it is in. It installs via apt-get, apk or dnf, whichever is there, and says plainly what
to do when none of them is. git goes in alongside, named in the step rather than smuggled
into it: checkout shells out to git the moment node has loaded it, so an image thin enough
to lack one usually lacks the other, and finding that out separately costs another round
trip through CI.

The version is warned about, not enforced. Distributions pin nodejs to whatever shipped
with the release — Ubuntu 24.04 still serves 18, past end of life and older than these
actions declare — but act_runner hands an action whichever node is on PATH regardless of
what it asked for, and it generally works. A warning is the right weight for something that
explains a later inexplicable failure without being one.

Repeated verbatim in all three jobs. It cannot be a local composite action, since that
needs the checkout it exists to unblock, and YAML anchors that would deduplicate it are
rejected by GitHub's parser. Byte-identical across the three so a diff shows drift.

This is still a workaround. The fix is one line of the runner's own config.yaml pointing
container.image at an image that ships node, as Gitea's default
catthehacker/ubuntu:act-latest does; the step then costs a version check and nothing else.
Kept regardless, because a pipeline that silently depends on a runner being configured
correctly elsewhere fails confusingly when it is not.

Verified by running the step's own script in ubuntu:24.04 and alpine:3.20, which have
neither, and node:20-bookworm, which has both: installs where needed, no-ops where not, and
warns only on the node 18 that Ubuntu gives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-01 14:04:50 +02:00
co-authored by Claude Opus 5
parent 57d4b30557
commit a6af93148b
+149
View File
@@ -30,6 +30,67 @@ jobs:
name: build and test
runs-on: [linux]
steps:
# act_runner runs every `uses:` action with node inside the job container, and this
# runner's image has none — the run died on the first line of actions/checkout with
# "Cannot find: node in PATH". A `run:` step is shell rather than node, so this one
# can go first and unblock the rest.
#
# This is a workaround and the real fix is one line of the runner's own config.yaml:
# point container.image at an image that ships node, the way Gitea's default
# catthehacker/ubuntu:act-latest does. Kept anyway, because a pipeline that depends
# on a runner being configured correctly somewhere else fails confusingly when it is
# not, and because it costs nothing on a runner that is.
#
# git as well as node, and said in the step name rather than smuggled in: checkout
# shells out to git the moment node has loaded it, so an image thin enough to lack
# one usually lacks the other, and learning that costs a whole second CI round trip.
#
# Repeated verbatim in all three jobs, which is not laziness. It cannot be a local
# composite action — that would need the checkout it exists to unblock — and YAML
# anchors, which would deduplicate it, are rejected by GitHub's parser and would make
# this file portable to nothing. Change one copy, change all three.
- name: ensure node and git
run: |
set -eu
SUDO=""
[ "$(id -u)" -eq 0 ] || SUDO="sudo"
missing=""
command -v node >/dev/null 2>&1 || missing="$missing nodejs"
command -v git >/dev/null 2>&1 || missing="$missing git"
if [ -z "$missing" ]; then
echo "node $(node --version), git $(git --version)"
exit 0
fi
echo "Installing:$missing"
if command -v apt-get >/dev/null 2>&1; then
$SUDO apt-get update -qq
$SUDO apt-get install -y --no-install-recommends $missing
elif command -v apk >/dev/null 2>&1; then
$SUDO apk add --no-cache $missing
elif command -v dnf >/dev/null 2>&1; then
$SUDO dnf install -y $missing
else
echo "No apt-get, apk or dnf here, so node cannot be installed from inside the" >&2
echo "job. Point the runner's container.image at something that ships node." >&2
exit 1
fi
echo "node $(node --version), git $(git --version)"
# Warned about rather than failed on. Distributions pin their nodejs package to
# the release they shipped with — Ubuntu 24.04 still serves 18, which is past end
# of life and older than the runtime these actions declare. It generally runs
# them anyway, since act_runner uses whichever node is on PATH regardless of what
# the action asked for, so this is a note for when one of them misbehaves in a
# way that makes no sense, not a reason to stop a build that is probably fine.
major="$(node --version | sed 's/^v//; s/\..*//')"
if [ "$major" -lt 20 ]; then
echo "::warning::node $major is older than the runtime these actions target;" \
"give the runner an image with node 20 or newer if actions misbehave."
fi
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
@@ -61,6 +122,50 @@ jobs:
name: android head
runs-on: [linux]
steps:
# Duplicated from the build job; see the comment there for why it cannot be factored
# out. Any change here has to be made in all three.
- name: ensure node and git
run: |
set -eu
SUDO=""
[ "$(id -u)" -eq 0 ] || SUDO="sudo"
missing=""
command -v node >/dev/null 2>&1 || missing="$missing nodejs"
command -v git >/dev/null 2>&1 || missing="$missing git"
if [ -z "$missing" ]; then
echo "node $(node --version), git $(git --version)"
exit 0
fi
echo "Installing:$missing"
if command -v apt-get >/dev/null 2>&1; then
$SUDO apt-get update -qq
$SUDO apt-get install -y --no-install-recommends $missing
elif command -v apk >/dev/null 2>&1; then
$SUDO apk add --no-cache $missing
elif command -v dnf >/dev/null 2>&1; then
$SUDO dnf install -y $missing
else
echo "No apt-get, apk or dnf here, so node cannot be installed from inside the" >&2
echo "job. Point the runner's container.image at something that ships node." >&2
exit 1
fi
echo "node $(node --version), git $(git --version)"
# Warned about rather than failed on. Distributions pin their nodejs package to
# the release they shipped with — Ubuntu 24.04 still serves 18, which is past end
# of life and older than the runtime these actions declare. It generally runs
# them anyway, since act_runner uses whichever node is on PATH regardless of what
# the action asked for, so this is a note for when one of them misbehaves in a
# way that makes no sense, not a reason to stop a build that is probably fine.
major="$(node --version | sed 's/^v//; s/\..*//')"
if [ "$major" -lt 20 ]; then
echo "::warning::node $major is older than the runtime these actions target;" \
"give the runner an image with node 20 or newer if actions misbehave."
fi
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
@@ -114,6 +219,50 @@ jobs:
needs: [build]
runs-on: [linux]
steps:
# Duplicated from the build job; see the comment there for why it cannot be factored
# out. Any change here has to be made in all three.
- name: ensure node and git
run: |
set -eu
SUDO=""
[ "$(id -u)" -eq 0 ] || SUDO="sudo"
missing=""
command -v node >/dev/null 2>&1 || missing="$missing nodejs"
command -v git >/dev/null 2>&1 || missing="$missing git"
if [ -z "$missing" ]; then
echo "node $(node --version), git $(git --version)"
exit 0
fi
echo "Installing:$missing"
if command -v apt-get >/dev/null 2>&1; then
$SUDO apt-get update -qq
$SUDO apt-get install -y --no-install-recommends $missing
elif command -v apk >/dev/null 2>&1; then
$SUDO apk add --no-cache $missing
elif command -v dnf >/dev/null 2>&1; then
$SUDO dnf install -y $missing
else
echo "No apt-get, apk or dnf here, so node cannot be installed from inside the" >&2
echo "job. Point the runner's container.image at something that ships node." >&2
exit 1
fi
echo "node $(node --version), git $(git --version)"
# Warned about rather than failed on. Distributions pin their nodejs package to
# the release they shipped with — Ubuntu 24.04 still serves 18, which is past end
# of life and older than the runtime these actions declare. It generally runs
# them anyway, since act_runner uses whichever node is on PATH regardless of what
# the action asked for, so this is a note for when one of them misbehaves in a
# way that makes no sense, not a reason to stop a build that is probably fine.
major="$(node --version | sed 's/^v//; s/\..*//')"
if [ "$major" -lt 20 ]; then
echo "::warning::node $major is older than the runtime these actions target;" \
"give the runner an image with node 20 or newer if actions misbehave."
fi
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# No docker/* actions here, deliberately. The build is single-architecture, so it