Skip to main content
Use the needs syntax in GitHub Actions to control job execution order. In this tutorial, you’ll chain build_job_1, test_job_2, and deploy_job_3 so that each runs only after its dependency succeeds.

How needs Works

The needs keyword, defined at the job level, accepts a single job name or an array of job names. A job won’t start until all its specified dependencies complete successfully.

1. Basic Build + Test Workflow

Here’s a minimal workflow where test_job_2 waits for build_job_1:
name: Generate ASCII Artwork
on:
  push:

jobs:
  build_job_1:
    runs-on: ubuntu-latest
    steps:
      - name: Install Cowsay
        run: sudo apt-get install cowsay -y
      - name: Generate message
        run: cowsay -f dragon "Run for cover, I am a DRAGON....RAWR" >> dragon.txt
      - name: Pause for 30 seconds
        run: sleep 30

  test_job_2:
    needs: build_job_1
    runs-on: ubuntu-latest
    steps:
      - name: Pause for 10 seconds
        run: sleep 10
      - name: Verify file exists
        run: test -f dragon.txt
With this setup, test_job_2 only starts once build_job_1 finishes without errors.

2. Detecting Cyclic Dependencies

If you introduce a cycle, GitHub Actions will reject your workflow before it runs.
Circular dependencies (e.g., build_job_1 needs test_job_2 and vice versa) are invalid. The runner throws an error on push.
Invalid example:
jobs:
  build_job_1:
    needs: test_job_2   # ❌ Invalid: cycle detected
    runs-on: ubuntu-latest
    steps:
      - run: echo "Building..."

  test_job_2:
    needs: build_job_1
    runs-on: ubuntu-latest
    steps:
      - run: echo "Testing..."

3. Adding a Deploy Phase

You can chain multiple jobs by listing dependencies as an array. Below is a full build–test–deploy sequence:
name: Generate ASCII Artwork
on:
  push:

jobs:
  build_job_1:
    runs-on: ubuntu-latest
    steps:
      - name: Install Cowsay
        run: sudo apt-get install cowsay -y
      - name: Generate message
        run: cowsay -f dragon "Run for cover, I am a DRAGON....RAWR" >> dragon.txt
      - name: Pause for 30 seconds
        run: sleep 30

  test_job_2:
    needs: build_job_1
    runs-on: ubuntu-latest
    steps:
      - name: Pause for 10 seconds
        run: sleep 10
      - name: Verify content
        run: grep -i "dragon" dragon.txt

  deploy_job_3:
    needs: [test_job_2]
    runs-on: ubuntu-latest
    steps:
      - name: Display file
        run: cat dragon.txt

Job Dependency Table

JobneedsPurpose
build_job_1Installs Cowsay and generates dragon.txt
test_job_2build_job_1Verifies that dragon.txt contains “dragon”
deploy_job_3[test_job_2]Outputs the contents of dragon.txt

4. Observing the Workflow Run

When you push this workflow:
  1. The GitHub Actions graph displays:
    build_job_1 → test_job_2 → deploy_job_3
  2. Each job runs on a separate runner; files are not shared by default.
  3. If test_job_2 fails to find dragon.txt, the runner skips deploy_job_3:
grep -i "dragon" dragon.txt
# exit status 1
The image shows a GitHub Actions workflow interface with a failed job sequence, where "test_job_2" has failed, causing the overall status to be marked as "Failure."
To share files between jobs, explicitly upload and download artifacts using the actions/upload-artifact and actions/download-artifact actions.

References