GitHub Actions Tutorial — Run Commands

3 minute read

Run Commands

The run: keyword executes shell commands on the runner. It’s the simplest and most direct way to do work in a workflow.


Single-Line Commands

steps:
  - run: echo "Hello, World!"
  - run: npm install
  - run: python -m pytest

Each run: step launches a new shell process. Environment variables set in one run: step don’t carry over to the next (use $GITHUB_ENV for that — covered in Step Outputs).


Multi-Line Commands

Use the | (pipe) character for multi-line scripts:

steps:
  - name: Build and test
    run: |
      echo "Installing dependencies..."
      npm ci
      echo "Running tests..."
      npm test
      echo "Building..."
      npm run build

All lines run in the same shell process, so variables set on one line are available on the next:

steps:
  - name: Set and use variable
    run: |
      MY_VAR="hello"
      echo "Value is: $MY_VAR"

Working Directory

By default, commands run in $GITHUB_WORKSPACE (the root of your checked-out repo). Change this with working-directory::

steps:
  - uses: actions/checkout@v4

  - name: Install frontend deps
    working-directory: ./frontend
    run: npm ci

  - name: Install backend deps
    working-directory: ./backend
    run: pip install -r requirements.txt

Custom Shell

By default:

  • Linux/macOS: bash
  • Windows: pwsh (PowerShell Core)

Override with shell::

steps:
  - name: Bash script
    shell: bash
    run: echo "Running in bash"

  - name: Python script
    shell: python
    run: |
      import platform
      print(f"Python on {platform.system()}")

  - name: PowerShell script
    shell: pwsh
    run: Write-Host "Running in PowerShell"

Available Shells

Shell Platforms Invocation
bash Linux, macOS, Windows bash --noprofile --norc -eo pipefail
sh Linux, macOS sh -e
pwsh All platforms pwsh -command
powershell Windows only powershell -command
python All platforms python {0}
cmd Windows only cmd /D /E:ON /V:OFF /S /C

Exit Codes and Error Handling

By default, if any command in a run: step returns a non-zero exit code, the step fails and the job stops.

Bash Error Behavior

GitHub Actions runs bash with set -eo pipefail:

  • -e: Exit on any error
  • -o pipefail: Pipe fails if any command in the pipeline fails
steps:
  - name: This fails the step
    run: |
      echo "This runs"
      false            # exit code 1 — step fails here
      echo "Never runs"

Continue Despite Errors

steps:
  - name: Allow failure
    continue-on-error: true
    run: |
      echo "Trying something risky..."
      exit 1

  - name: This still runs
    run: echo "Previous step failed, but we continue"

Handling Errors in Script

steps:
  - name: Custom error handling
    run: |
      if ! npm test; then
        echo "Tests failed — collecting diagnostics"
        npm run report
        exit 1
      fi

Step Name and ID

steps:
  - name: "Install Dependencies"    # display name in UI
    id: install                      # reference ID for outputs
    run: npm ci
  • name: appears in the Actions UI (optional but recommended)
  • id: is required if you need to reference the step’s outputs or outcome

Conditional Steps


steps:
  - name: Deploy to production
    if: github.ref == 'refs/heads/main'
    run: ./deploy.sh production

  - name: Deploy to staging
    if: github.ref == 'refs/heads/develop'
    run: ./deploy.sh staging

  - name: Always cleanup
    if: always()
    run: ./cleanup.sh


Timeouts

Set a per-step timeout (default is 6 hours):

steps:
  - name: Quick operation
    timeout-minutes: 5
    run: npm test

Common Mistakes

Mistake Fix
Variable not available in next run: step Each run: is a new shell. Use $GITHUB_ENV to persist variables.
Multi-line command fails silently Bash uses set -eo pipefail by default; check your pipe commands
Wrong shell on Windows Windows defaults to pwsh; add shell: bash if you need bash
cd doesn’t persist cd in one run: doesn’t affect the next. Use working-directory: instead.

Quick Reference

Feature Syntax
Single command run: echo "hello"
Multi-line run: \| then indented lines
Working directory working-directory: ./subdir
Custom shell shell: python
Continue on error continue-on-error: true
Timeout timeout-minutes: 5
Conditional if: github.ref == 'refs/heads/main'

← Back to: Chapter 5: Steps & Actions

Next: Using Actions →

Updated: