Course outline

GitHub Actions / Events, execution, and data boundaries / Lesson 1

Workflow, job, step, and action boundaries

Read an Actions definition as an event-driven execution graph with distinct state boundaries.

About 4 min readContent reviewed
On this page

Lesson goals

  • Distinguish a workflow run, job, step, and reusable action.
  • Explain which state survives a step boundary and which does not cross jobs.

Name the object being discussed

GitHub Actions reads workflow definitions from the repository’s .github/workflows directory. An event can create a workflow run containing jobs, and each job contains ordered steps or calls a reusable workflow through a different job form. A step can execute a shell command with run or invoke an action with uses. These objects have different identities and failure behavior. In the synthetic Parcel repository, label-checks is a workflow, formatting is a job, and normalize-fixture is a step. Avoid calling all three the build when diagnosing a missing result.

Read a minimal definition structurally

The following illustration has one push event and one job with two shell steps. It would execute if placed in an enabled repository, so this course displays it only as non-runnable text and never creates an actual workflow file. ubuntu-latest is a moving hosted-runner label, not an immutable operating-system image. The printed line is ordinary shell output; it does not create an artifact or prove application correctness. Every meaningful check needs an assertion or an explicit command whose status represents the intended contract.

yaml Read-only workflow illustration
name: Parcel model
on: push
permissions:
  contents: read
jobs:
  formatting:
    runs-on: ubuntu-latest
    steps:
      - name: Introduce fixture
        run: printf "%s\n" "parcel-lab"
      - name: Check declared value
        run: test "parcel-lab" = "parcel-lab"

The first step would print parcel-lab and the second would exit zero under a suitable shell. Neither checks repository files, and no checkout action is present. No GitHub run is created or verified here.

Steps share files but not one shell process

Shell steps in one job normally run in separate processes while sharing the job’s workspace. A shell variable assigned in one step does not automatically appear in the next. Files can remain available within the job, and GitHub provides environment files and outputs for intentional data transfer. Do not confuse those mechanisms with process memory. A command such as cd changes only the current step’s shell directory unless a later step also selects a working directory through its own configuration.

Jobs need explicit data relationships

Separate jobs may execute on different runners and do not share a workspace merely because their runs-on values match. needs controls scheduling dependencies and exposes declared job outputs; artifacts transfer files through explicit upload and download operations. A job can be skipped if its conditions are false or dependencies do not satisfy the configured behavior. Treat the job result as evidence for that specific execution, not a permanent fact about a branch name that can move after the run starts.

Demonstrate a process boundary locally

This complete Python program launches one isolated child process with a temporary environment variable and then checks the parent. It illustrates process-local state, not GitHub’s environment-file implementation. The child receives an explicit environment mapping; its output cannot mutate the parent’s environment. Real shell quoting and runner defaults still depend on the selected shell and platform. Use small boundary models to clarify reasoning while preserving the distinction between local evidence and an actual hosted-runner test.

python A child process does not modify its parent
import os, subprocess, sys
env = {"PARCEL_STEP": "first"}
r = subprocess.run([sys.executable, "-I", "-c", "import os; print(os.environ['PARCEL_STEP'])"], env=env, capture_output=True, text=True, check=True)
print(r.stdout.strip())
print("PARCEL_STEP" in os.environ)
Expected output
first
False

The program creates only a local child Python process and performs no network or CI operation. The test environment must not already define PARCEL_STEP; the authoring check uses that declared condition.

Common errors and fixes

A later step cannot read an earlier shell variable.

Why it happens
The author assumed all steps shared one shell process.
How to fix it
Use a declared output or environment file for the intended scope.
Prevent it next time
Document whether each value belongs to a step, job, or downstream job.

A downstream job cannot find a generated file.

Why it happens
Matching runner labels were mistaken for shared storage.
How to fix it
Transfer the declared artifact explicitly and verify its producer.
Prevent it next time
Review scheduling edges separately from data movement.

A print-only workflow is described as testing the application.

Why it happens
Successful logging was confused with an asserted application contract.
How to fix it
Add meaningful checks with failure statuses tied to requirements.
Prevent it next time
State what every required job proves and its exclusions.

Check your understanding

1. A run contains two jobs, each with three steps. Must all six steps execute sequentially?

Show worked solution

No. Steps are ordered within each job, while independent jobs can run concurrently when capacity and conditions allow. Add needs only for genuine dependencies. A sequential drawing of the YAML does not establish a cross-job order.

2. Step one changes directory and creates a file. What should step two assume?

Show worked solution

The file can remain in the job workspace, but the second shell does not inherit the first shell’s current directory or local variables. Select the intended working directory explicitly and refer to the file through a known path.

Key takeaways

  • Use workflow, job, and step names precisely.
  • Process state, workspace files, outputs, and artifacts have different scopes.

Official references