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.
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.
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.
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)
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.