Summary
lychee-action fails with lychee: command not found on self-hosted runners (and any environment) where $HOME resolves to a different path between composite-action steps. The action ends up adding one directory to $GITHUB_PATH and installing the binary into a different one.
Where it goes wrong
In action.yml, $HOME is evaluated independently in two separate bash shells:
- name: Set up environment
run: |
echo "$HOME/.local/bin" >> "$GITHUB_PATH" # ← $HOME #1
mkdir -p "$HOME/.local/bin"
shell: bash
# ...later...
- name: Install lychee
run: |
install -t "$HOME/.local/bin" -D "${{ steps.lychee-setup.outputs.temp_dir }}/lychee" # ← $HOME #2
shell: bash
On ubuntu-latest both $HOMEs resolve to /home/runner, so it works. But on runners where each step gets a fresh ephemeral $HOME (we hit this on an internal self-hosted runner; actions/checkout itself logs Temporarily overriding HOME='/workspace/_temp/<uuid>' post-step), the two paths diverge. PATH points at the old $HOME, install lands in the new one, and entrypoint.sh errors with:
entrypoint.sh: line 49: lychee: command not found
##[error]Process completed with exit code 127.
Repro
Any runner that resets/overrides $HOME between composite-action steps. The clearest sign in logs is:
- Step "Install lychee" reports success
- Step "Run Lychee" immediately errors with
lychee: command not found
- Earlier setup step also added a different
$HOME/.local/bin to $GITHUB_PATH
Suggested fix
Install into a path that does not depend on $HOME. The natural choice is $RUNNER_TEMP — guaranteed-writable, stable across steps within a job, and exactly what it's for:
- name: Set up environment
run: |
echo "$RUNNER_TEMP/lychee/bin" >> "$GITHUB_PATH"
mkdir -p "$RUNNER_TEMP/lychee/bin"
shell: bash
…and use $RUNNER_TEMP/lychee/bin in the install step too. This also makes the action robust in containers where $HOME may be unset or read-only.
For comparison, jdx/mise-action is a JS action and uses the Actions toolkit's core.addPath() from inside Node — same intent, but it sidesteps the "$HOME evaluated twice in two shells" pitfall entirely.
Summary
lychee-actionfails withlychee: command not foundon self-hosted runners (and any environment) where$HOMEresolves to a different path between composite-action steps. The action ends up adding one directory to$GITHUB_PATHand installing the binary into a different one.Where it goes wrong
In
action.yml,$HOMEis evaluated independently in two separatebashshells:On
ubuntu-latestboth$HOMEs resolve to/home/runner, so it works. But on runners where each step gets a fresh ephemeral$HOME(we hit this on an internal self-hosted runner;actions/checkoutitself logsTemporarily overriding HOME='/workspace/_temp/<uuid>'post-step), the two paths diverge. PATH points at the old$HOME, install lands in the new one, andentrypoint.sherrors with:Repro
Any runner that resets/overrides
$HOMEbetween composite-action steps. The clearest sign in logs is:lychee: command not found$HOME/.local/binto$GITHUB_PATHSuggested fix
Install into a path that does not depend on
$HOME. The natural choice is$RUNNER_TEMP— guaranteed-writable, stable across steps within a job, and exactly what it's for:…and use
$RUNNER_TEMP/lychee/binin the install step too. This also makes the action robust in containers where$HOMEmay be unset or read-only.For comparison,
jdx/mise-actionis a JS action and uses the Actions toolkit'score.addPath()from inside Node — same intent, but it sidesteps the "$HOME evaluated twice in two shells" pitfall entirely.