A deploy job that removed one name: line began searching for release-input/site.tar.gz, while actions/download-artifact@v4 had correctly placed the file at release-input/web-package/site.tar.gz. The workflow still passed its build job, still downloaded an artifact, and failed only when the release script tried to extract a path that no longer existed.

That is the migration risk worth testing: not whether GitHub Actions can download the artifact, but whether the downloaded filesystem still satisfies the contract your deploy command assumes. Artifact v4 improves availability and uses a new artifact backend, but it also draws a hard compatibility line with v1–v3 artifacts and makes selector choice visible in the directory layout.

1. Start with a filesystem contract, not a version bump

Before changing either action version, write down the exact file that the deploy job consumes. In a build-and-deploy pipeline, the meaningful contract is usually one archive, such as site.tar.gz, plus a small identity file such as build-sha.txt. The upload name is an artifact identifier; it is not always a directory that will appear below your requested download path.

For this guide, the build job uploads one artifact named web-package. Its contents are a compressed static site archive and a commit marker. The deploy job must download the archive before running its extraction command.

Contract element Expected value Why it matters
Artifact name web-package The selector used by the deploy job.
Archive file site.tar.gz The file passed to tar.
Identity marker build-sha.txt Confirms which build output was selected.
Named-download destination release-input/site.tar.gz The correct v4 path when name: web-package is present.

This contract prevents a common incorrect workaround: adding an artifact-name directory to every deploy path. That directory is expected when downloading all artifacts, but it is not the normal result of downloading one artifact by name into a specified path.

2. Test the selector first: v4 layout depends on how you download

The same uploaded artifact can produce three different layouts. Treat name, no selector, and pattern plus merge-multiple as separate behaviors rather than interchangeable refactors. A deploy job consuming one archive should normally use the first row.

Download selector Requested path Expected v4 result Migration meaning
name: web-package release-input release-input/site.tar.gz Single-artifact deploy; keep existing archive-oriented paths.
No name; download all artifacts release-input release-input/web-package/site.tar.gz Artifact names become directory names.
pattern: web-package-* with merge-multiple: true release-input Matched files merge directly under release-input Only safe when duplicate filenames are deliberately handled.

Run this diagnostic in a temporary migration branch immediately after the download step. It prints the actual tree rather than relying on a mental model carried over from an older workflow.

- name: Inspect downloaded artifact layout
  shell: bash
  run: |
    echo "Downloaded files:"
    find release-input -maxdepth 3 -type f -print | sort
    test -f release-input/site.tar.gz
    test -f release-input/build-sha.txt

If this assertion fails, do not change the assertion until you decide whether the selector changed intentionally. A missing name is a semantic change: it expands the input set from one known artifact to every artifact in the selected run.

3. Upgrade upload and download together across the compatibility boundary

Artifacts v4 is not cross-compatible with previous artifact versions. An artifact uploaded with actions/upload-artifact@v3 cannot be consumed by actions/download-artifact@v4. The reliable migration unit is therefore the producer and consumer pair, not the individual YAML line that happens to fail first.

Producer Consumer Migration result Recommended action
upload-artifact@v3 download-artifact@v3 Legacy-compatible pair Use only as the pre-migration baseline.
upload-artifact@v4 download-artifact@v4 Compatible v4 pair Use for the migrated build-and-deploy path.
upload-artifact@v3 download-artifact@v4 Not cross-compatible Do not mix versions within one artifact handoff.
upload-artifact@v4 download-artifact@v3 Do not treat as a supported migration path Upgrade the consumer with the producer.

This matters when repositories have reusable workflows. A central build workflow may upload artifacts while application repositories own deployment workflows. Search both repositories before merging: the visible deployment file may not contain the upload action that determines compatibility.

git grep -nE 'actions/(upload-artifact|download-artifact)@v[1-4]' \
  -- .github/workflows

Also inspect workflow files called through uses: owner/repo/.github/workflows/file.yml@ref. Version compatibility is a pipeline property, not merely a property of one job.

4. Make the build artifact inspectable before migrating deployment

Use an archive plus a marker file rather than uploading an unstructured build directory. A tar archive gives the deploy job one stable payload filename, and it is also useful when executable permissions matter because the archive records file modes. The marker makes the selected build observable without requiring a production deploy.

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      build_sha: ${{ steps.identity.outputs.sha }}
    steps:
      - uses: actions/checkout@v4

      - name: Build site
        run: |
          npm ci
          npm run build
          tar -czf site.tar.gz -C dist .

      - name: Write build identity
        id: identity
        shell: bash
        run: |
          printf '%s\n' "$GITHUB_SHA" > build-sha.txt
          echo "sha=$GITHUB_SHA" >> "$GITHUB_OUTPUT"

      - name: Upload deploy package
        uses: actions/upload-artifact@v4
        with:
          name: web-package
          path: |
            site.tar.gz
            build-sha.txt
          if-no-files-found: error

In a same-run downstream job, $GITHUB_SHA already identifies the same workflow revision. Therefore, this marker primarily proves that the downloaded archive set contains the expected files; it does not independently prove that a different commit was selected. It becomes essential when the deploy workflow selects another run or repository, covered in section 8.

5. Compare the old and new download layouts before changing deploy commands

A migration review should include the selector-to-path matrix next to the workflow diff. This catches the mistake where a reviewer sees a valid v4 action and misses that the action is no longer downloading the same set of inputs.

Configuration Files below release-input Deploy command should reference
name: web-package site.tar.gz, build-sha.txt release-input/site.tar.gz
No name, download all web-package/site.tar.gz, plus one directory per artifact release-input/web-package/site.tar.gz
pattern and merge-multiple: true Matched files merged into the requested directory release-input/site.tar.gz, only if collisions are impossible or accepted

The migration-safe download step for this single-package deployment is explicit:

- name: Download deploy package
  uses: actions/download-artifact@v4
  with:
    name: web-package
    path: release-input

Do not rely on an unqualified “download all” because it happens to find the package today. A later test-report artifact, SBOM, or debug-log upload changes the deploy job’s input set without touching the deploy YAML.

6. Keep name: web-package when one archive is the deploy input

The deploy decision is straightforward: if the job consumes one archive, retain name: web-package. Removing name is not a cleanup refactor; it changes the meaning from “download this release package” to “download every artifact from this run.” That change requires either updating the archive path to include web-package or deliberately treating artifact-name directories as part of the deployment contract.

Here is a complete downstream job for the named-artifact case. Notice that the assertions occur before extraction and before any remote deployment command.

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download deploy package
        uses: actions/download-artifact@v4
        with:
          name: web-package
          path: release-input

      - name: Verify release input
        shell: bash
        run: |
          set -euo pipefail
          test -f release-input/site.tar.gz
          test -f release-input/build-sha.txt
          test "$(cat release-input/build-sha.txt)" = "$GITHUB_SHA"
          tar -tzf release-input/site.tar.gz | head -n 20

      - name: Extract release
        shell: bash
        run: |
          mkdir -p release-output
          tar -xzf release-input/site.tar.gz -C release-output

The needs: build dependency still matters even though v4 artifacts become available after upload. Immediate availability helps API and artifact access behavior, but it does not replace the ordering requirement for a deploy job that must wait for a successful build.

7. Test v4 with a failure-oriented pull request workflow

Do not make production deployment the first test of artifact v4. Add a pull-request-safe verification job that builds, uploads, downloads, lists the result, checks the marker, and validates the archive. This exercises the actual artifact backend and the actual runner filesystem rather than only validating YAML syntax.

  1. Upgrade the build job to actions/upload-artifact@v4.
  2. Upgrade the consuming test or deploy-preflight job to actions/download-artifact@v4.
  3. Keep the intended selector explicit: use name for one release package.
  4. Print a bounded directory tree with find.
  5. Fail on missing files with test -f before invoking cloud, SSH, or package-manager deployment steps.
- name: Assert artifact contract
  shell: bash
  run: |
    set -euo pipefail
    find release-input -maxdepth 3 -type f -print | sort
    test -s release-input/site.tar.gz
    test -s release-input/build-sha.txt
    tar -tzf release-input/site.tar.gz > /dev/null

The tradeoff nobody mentions is diagnosis time. Without these four checks, a wrong path may surface later as an empty upload, a missing container build context, or a deployment tool’s generic “file not found” message. With them, the failed job names the broken artifact contract directly.

8. Add stronger identity checks for artifacts from another run or repository

Downloading from another workflow run or repository is where a marker becomes more than a convenient diagnostic. The current deploy workflow’s GITHUB_SHA may not match the commit that produced the selected artifact, so comparing the marker to the deploy workflow SHA can reject a valid release or accept the wrong assumption.

Instead, make the producing build run emit its commit SHA as an explicit output or publish it through the promotion mechanism that selects the run. For example, a dispatch input named expected_build_sha can travel with the selected run_id.

on:
  workflow_dispatch:
    inputs:
      build_run_id:
        required: true
        type: string
      expected_build_sha:
        required: true
        type: string

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Download selected build artifact
        uses: actions/download-artifact@v4
        with:
          name: web-package
          path: release-input
          run-id: ${{ inputs.build_run_id }}
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Verify selected build identity
        shell: bash
        run: |
          set -euo pipefail
          test "$(cat release-input/build-sha.txt)" = "${{ inputs.expected_build_sha }}"
          test -f release-input/site.tar.gz

Use the token and repository inputs required by your cross-repository design, with only the permissions needed to read the chosen artifact. The key migration rule is independent of the authorization model: validate against the selected build’s identity, not automatically against the deploy run’s identity.

9. Ship the migration this week with a narrow rollout checklist

Make one artifact handoff boring before standardizing every workflow in the organization. Start with a build-to-deploy path that has a single named package, then use the resulting assertions and path contract as a reusable workflow template.

  • Search workflow repositories for both upload and download action versions.
  • Upgrade each producer-consumer pair to v4 together.
  • Document whether every consumer downloads one named artifact, all artifacts, or a merged pattern.
  • Add the selector-to-layout matrix to the pull request description for deploy-related changes.
  • Run find, test -f, and archive validation in a non-production workflow run.
  • Keep name: web-package for a job that consumes one archive.
  • For cross-run promotion, compare build-sha.txt with an expected SHA supplied by the selected build.

The successful v4 migration is not the one where every action reference says @v4. It is the one where the artifact producer, selector, download directory, validation command, and deploy script all agree on the same concrete path.