Do ARM64 Runners Use a Different Work Directory?

Yes. Ubuntu 24.04 ARM64 runners set the work directory to /runner/_work instead of the /home/runner/work path GitHub-hosted runners use. Read GITHUB_WORKSPACE.

Yes. The arm64 images for Ubuntu 24.04 set the runner work directory to /runner/_work, which differs from GitHub's work directory /home/runner/work/ for the same instance (WarpBuild cloud runners documentation, checked on 2026-08-13). The layout under that prefix is unchanged and every path variable still resolves, so a workflow breaks only where it hardcodes the literal /home/runner/work string.

Answer

The prefix moves. Nothing under it does.

ValueGitHub-hosted runnerUbuntu 24.04 ARM64 runner
Runner work directory/home/runner/work/runner/_work
Checkout of a repository named api/home/runner/work/api/api/runner/_work/api/api
A file at dist/app.tar.gz in that checkout/home/runner/work/api/api/dist/app.tar.gz/runner/_work/api/api/dist/app.tar.gz

The work directory is a property of how the runner agent was configured when the image was built, not a property of the architecture. The agent takes the directory as a configuration argument at registration time (actions/runner), and GitHub then exposes the resolved paths to your steps through default environment variables (GitHub variables reference). A step that reads those variables gets the right answer on any runner. A step that repeats the GitHub-hosted string gets the wrong answer on ARM64.

Four things in a typical repository carry that literal:

  1. run: steps that build an absolute path by hand, usually to hand a file to a tool that wants one.
  2. actions/cache entries whose path: was copied from a log line rather than written relative to the workspace.
  3. Docker -v mounts and --mount sources in jobs that shell out to docker run.
  4. Config files checked into the repository: sonar-project.properties, coverage exclusion lists, Makefile variables, and anything else holding a build path.

The replacement is one of two forms depending on where you are writing:

Do not writeIn YAML, writeIn a shell step, write
/home/runner/work/api/api${{ github.workspace }}$GITHUB_WORKSPACE
/home/runner/work/_temp${{ runner.temp }}$RUNNER_TEMP
A hosted tool cache path${{ runner.tool_cache }}$RUNNER_TOOL_CACHE

The github and runner contexts are documented in the GitHub contexts reference. Both forms are populated by the runner agent, so the same workflow file runs unchanged on warp-ubuntu-latest-arm64-4x, on warp-ubuntu-latest-x64-4x, and on a GitHub-hosted runner.

name: build

on:
  pull_request:

jobs:
  build:
    runs-on: warp-ubuntu-latest-arm64-4x
    steps:
      - uses: actions/checkout@v4

      - name: Print the paths this runner reports
        run: |
          echo "workspace: $GITHUB_WORKSPACE"
          echo "temp:      $RUNNER_TEMP"
          echo "arch:      ${{ runner.arch }}"

      - uses: actions/cache@v4
        with:
          path: ~/.cargo/registry
          key: cargo-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Cargo.lock') }}

      - name: Build
        run: cargo build --release --target-dir "$GITHUB_WORKSPACE/target"

      - name: Stage the archive
        run: |
          tar -czf "$RUNNER_TEMP/app.tar.gz" -C "$GITHUB_WORKSPACE/target/release" app

      - uses: actions/upload-artifact@v4
        with:
          name: app-linux-arm64
          path: ${{ runner.temp }}/app.tar.gz

WarpBuild provides Linux x64, Linux ARM64, macOS, and Windows runners, and the ARM64 line runs from warp-ubuntu-latest-arm64-2x at 2 vCPU and 8 GB for $0.003 per minute to warp-ubuntu-latest-arm64-32x at 32 vCPU and 128 GB for $0.048 per minute (cloud runners documentation, checked on 2026-08-13). Every label and rate is on the Linux ARM64 runner catalog and the pricing page.

Detail

What the home directory difference is not

Two paths get confused here. The work directory holds checkouts, the temp directory, and the per-job scratch space. The home directory holds per-user caches such as ~/.cargo, ~/.npm, ~/.m2, and ~/.gradle. Moving the work directory does not move the home directory, so a cache entry written as ~/.npm or $HOME/.gradle/caches behaves the same on both images. Audit the literals that begin with /home/runner/work, and leave the ~ entries alone.

The grep pass to run before the migration

Run this from the repository root. It takes a minute and it finds the entries that would otherwise fail on the first ARM64 job.

# 1. Workflow files and composite actions
grep -rn --include='*.yml' --include='*.yaml' \
  -e '/home/runner' -e '_work' .github/

# 2. Anything checked in that a build tool reads
grep -rn -e '/home/runner' \
  --include='*.properties' --include='*.toml' --include='*.cfg' \
  --include='*.json' --include='Makefile' --include='Dockerfile*' .

# 3. Shell and build helper scripts
grep -rn -e '/home/runner' -e 'hostedtoolcache' scripts/ ci/ 2>/dev/null

# 4. Docker volume mounts written inline
grep -rn -e 'docker run' -A2 .github/ | grep -e '-v /' -e '--mount'

Work through the hits in this order:

  • Workflow run: steps. Replace the literal with $GITHUB_WORKSPACE or $RUNNER_TEMP.
  • actions/cache and actions/upload-artifact path lists. Rewrite them relative to the workspace, which is the default working directory for a step, so a bare dist/ is usually enough.
  • Docker mounts. Use -v "$GITHUB_WORKSPACE:/src" and keep the container side of the mount fixed.
  • Checked-in config. Where a tool insists on an absolute base directory, pass it as a flag from the workflow rather than storing it in the file, for example -Dsonar.projectBaseDir="$GITHUB_WORKSPACE".
  • Cached tool state that records absolute paths, such as compiler caches and coverage reports. Those entries are keyed to the path they were written under, so add runner.arch to the cache key and let the ARM64 leg build its own entry.

Proving it before you flip a production workflow

Add one job that runs on both architectures and prints what each runner reports. A matrix leg costs a few cents and it removes the guesswork.

jobs:
  paths:
    strategy:
      fail-fast: false
      matrix:
        runner:
          - warp-ubuntu-latest-x64-2x
          - warp-ubuntu-latest-arm64-2x
    runs-on: ${{ matrix.runner }}
    steps:
      - uses: actions/checkout@v4
      - name: Report paths
        run: |
          echo "runner:    ${{ matrix.runner }}"
          echo "pwd:       $(pwd)"
          echo "workspace: $GITHUB_WORKSPACE"
          echo "temp:      $RUNNER_TEMP"
          test "$(pwd)" = "$GITHUB_WORKSPACE"

The final test line turns an assumption into a failing step. Keep it in the repository as a smoke job while the migration is in flight.

When a real job fails and the log does not say why, the surrounding product surface is the faster route than adding print statements. The Action Debugger opens a shell on the failed runner so you can ls /runner/_work and read the actual layout, and CI observability shows whether the failure is on one architecture or both. Jobs that never reach a runner at all are a different problem, covered in the common issues documentation.

What stays the same

actions/checkout clones into GITHUB_WORKSPACE on both images, so the relative layout of the checkout is unchanged. Marketplace actions that use the toolkit path helpers resolve through the same variables. Service containers, job containers, artifacts, and the default working directory for run: steps all follow the workspace. The rest of the x64 to ARM64 move is covered in what changes when a workflow moves from x64 to ARM64, and the full sequence is in the ARM64 migration checklist for GitHub Actions.

Does the work directory difference apply to Ubuntu 26.04 ARM64 runners as well?

The difference is recorded against the arm64 Ubuntu 24.04 images, which the warp-ubuntu-latest-arm64 labels resolve to (cloud runners documentation). Rather than track the value per image, read the path from GITHUB_WORKSPACE and RUNNER_TEMP in shell steps and from ${{ github.workspace }} and ${{ runner.temp }} in YAML. Those resolve correctly on every image and on GitHub-hosted runners, which is why the variables exist. Both Ubuntu releases and all ten ARM64 labels are listed on the Linux ARM64 runner catalog.

Do I need to change my actions/cache paths when I move a job to ARM64?

Only the entries whose path: is a literal string starting with /home/runner/work. Paths written relative to the workspace or relative to the home directory keep working, because the work directory setting does not move the home directory. Add ${{ runner.arch }} to the cache key so the x64 and ARM64 legs of a matrix stop overwriting each other, and treat any cache holding compiled output or absolute-path metadata as per-architecture.

Does actions/checkout put the repository somewhere else on an ARM64 runner?

It clones into GITHUB_WORKSPACE, so the absolute path changes with the work directory while the layout under it stays the same. A repository named api lands at /runner/_work/api/api on the ARM64 image and at /home/runner/work/api/api on a GitHub-hosted runner. Steps that use relative paths see no difference at all.

Start with $10 in free credits

Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.