Skip to content

Find Erlang/OTP in the tool cache - #497

Open
rubas wants to merge 2 commits into
erlef:mainfrom
rubas:fix-otp-tool-cache
Open

rubas wants to merge 2 commits into
erlef:mainfrom
rubas:fix-otp-tool-cache

Conversation

@rubas

@rubas rubas commented Sep 28, 2026

Copy link
Copy Markdown

Description

setup-beam caches Erlang/OTP in the runner tool cache, but never finds it there again. Every run downloads OTP, also on self-hosted runners with a persistent tool cache.

Before

Two setup-beam steps in one job (erlef/setup-beam@v1.24.1, ubuntu-24.04, step debug logging on):

- uses: erlef/setup-beam@v1.24.1
  with:
    otp-version: '29'
- uses: erlef/setup-beam@v1.24.1
  with:
    otp-version: '29'

The second step, although $RUNNER_TOOL_CACHE/otp/ubuntu-24.04/OTP-29.1.1/x64.complete exists:

##[debug]Checking if Erlang/OTP is already cached...
##[debug]  ... it isn't!
##[debug]Downloading https://builds.hex.pm/builds/otp/amd64/ubuntu-24.04/OTP-29.1.1.tar.gz
##[debug]Caching tool otp ubuntu-24.04/OTP-29.1.1 x64

The second step took 9.7 s.

Problem

  • installTool looks up the cache with tc.find, which only accepts semver versions.
  • We cache OTP as <os>/<version>, e.g. ubuntu-24.04/OTP-29.1.1, which is not semver. So the lookup never matches, on any OS.
  • Elixir, Gleam, and rebar3 are not affected, because their versions are semver.

After

installTool now looks up the same path that tc.cacheDir and tc.cacheFile write:

function findInToolCache(toolName, versionSpec) {
  if (isKnownVerBranch(versionSpec) || versionSpec === 'nightly') {
    return ''
  }

  const cachePath = path.join(
    process.env.RUNNER_TOOL_CACHE,
    toolName,
    semver.clean(versionSpec) || versionSpec,
    os.arch(),
  )

  return fs.existsSync(cachePath) && fs.existsSync(`${cachePath}.complete`)
    ? cachePath
    : ''
}

The same job with this branch, second step:

##[debug]Checking if Erlang/OTP is already cached...
##[debug]  ... it is, at /opt/hostedtoolcache/otp/ubuntu-24.04/OTP-29.1.1/x64
##[debug]Performing post extract operations...
[command]/home/runner/work/_temp/.setup-beam/otp/Install -minimal /home/runner/work/_temp/.setup-beam/otp

The second step took 0.59 s.

Details

Cause. src/setup-beam.js:install builds versionSpec as `${osVersion}/${toolVersion}` for OTP. tc.cacheDir stores it as-is, because semver.clean fails. tc.find calls isExplicitVersion, which fails, then searches findAllVersions('otp'), which only lists semver folder names. The folder there is ubuntu-24.04, so it returns ''.

Design.

  • The lookup uses the same layout as tc.cacheDir (semver.clean(version) || version, then os.arch()), and checks the directory and its .complete marker, as tc.find does. Existing cache entries stay valid, and Elixir, Gleam, and rebar3 resolve to the same path as before.
  • Branch builds (main, master, maint, maint-27, nightly) change over time. tc.find never matched them before, so they were always downloaded again. The new lookup keeps that behavior with isKnownVerBranch, so a persistent cache does not serve an old build.
  • A cache hit still copies the cached files to RUNNER_TEMP and runs postExtract (Install -minimal, or the Windows installer), as before.

Tests. .findInToolCache(_) caches entries with the real tc.cacheDir and checks that the lookup finds an OTP entry (ubuntu-24.04/OTP-27.2) and an Elixir entry (v1.18.4-otp-27), skips a cached branch build (ubuntu-24.04/maint-27), and returns '' for a version that is not cached. It fails on main and passes with this change.

Checks.

  • npm run build-dist (build, format, lint): clean, dist/index.js rebuilt.
  • CI in our fork: ubuntu, macos, windows, and hexpm-mirrors pass, and npm test passes on macOS and Windows. On Ubuntu, one test fails: .getOTPVersion(_) - Elixir expects v1.18.4-otp-27 for 1.18.x, but the live builds.hex.pm list has v1.18.5-otp-27 since 2026-08-28. The failure is not caused by this change.
  • Workflow run with two setup-beam steps per job, before and after: https://github.com/rubas/setup-beam/actions/runs/36468681481 (attempt 2 has debug logging).

This change is independent of #494, which changes the RUNNER_TEMP install path in the same function.

tc.find only matches semver versions, so it never finds Erlang/OTP,
which we cache as e.g. ubuntu-24.04/OTP-27.2. Look up the path that
tc.cacheDir writes instead, and keep downloading branch builds.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant