GitHub Actions / Events, execution, and data boundaries / Lesson 2
YAML, contexts, and expression evaluation
Separate configuration parsing, Actions expression expansion, and shell execution.
On this page
Lesson goals
- Identify which interpreter processes a value.
- Avoid treating missing or string-valued context data as a typed boolean.
Three interpreters can touch one line
An Actions definition is YAML, GitHub evaluates expressions in supported fields, and the runner later executes shell text. A value can be valid YAML yet be invalid in the Actions schema or unsafe as shell input. In Parcel, an expression selecting a branch is evaluated by the control plane, while $PARCEL_MODE is expanded by the shell during a run step. Ask which layer owns each piece of syntax before adding quotes. Quotes meaningful to YAML do not automatically protect a value after expression substitution into a shell program.
Contexts exist at different times
The github context describes run and event information, runner describes the selected runner, matrix describes the expanded matrix entry, and needs exposes declared dependency information. Availability varies by field and execution stage. A job-level condition cannot rely on arbitrary files produced by a step that has not run. Default environment variables exist on the runner; contexts can be used during earlier routing decisions where supported. Consult the context availability table for the exact field instead of assuming every context is legal everywhere.
Missing properties can be misleading
A nonexistent context property evaluates to an empty string. Step outputs are strings unless explicitly converted, and a string containing false is different from a boolean false in many evaluation situations. Model the expected type and validate it before a release decision. The complete local program below demonstrates Python JSON type distinction only; it does not emulate GitHub’s expression coercion rules. Use the official expression reference for Actions-specific equality, truthiness, and conversion behavior rather than transferring another language’s rules uncritically.
import json
text = "false"
value = json.loads(text)
print(type(text).__name__)
print(type(value).__name__)
print(value is False)
str
bool
True
Python calls its boolean type bool. This offline example establishes a type distinction and JSON parsing result, not the exact semantics of every GitHub expression operator.
Pass data through an environment boundary
The YAML fragment below assigns a context value to an environment variable and reads it as quoted shell data. It avoids inserting the value directly into the script body. This is useful for untrusted event text, although the application consuming the value still needs validation. Environment variables do not make arbitrary data safe as a command name, option, filename, or SQL expression. Preserve the data boundary through every later operation and prefer structured APIs when shell interpretation is unnecessary.
env:
PARCEL_REF: ${{ github.ref }}
steps:
- name: Display selected ref
shell: bash
run: printf "%s\n" "$PARCEL_REF"
This is a job fragment and lacks a complete workflow. The printf format is fixed and the value is a quoted argument. The exact ref depends on the triggering event, so no fabricated run output is supplied.
Review conditions with negative examples
A condition should document what happens for an absent property, an empty output, false text, canceled dependencies, and an unexpected event. If a job depends on a predecessor, ordinary status-check behavior can skip it before its business expression matters. Explicit status functions must be chosen deliberately, particularly for diagnostics and cancellation. Keep required release predicates small enough to inspect. A clever expression combining coercion, fallback, and string formatting is harder to trust than a validated output with an explicit documented meaning.
Common errors and fixes
A shell variable is referenced during job routing.
- Why it happens
- Runner-time environment expansion was confused with control-plane expression evaluation.
- How to fix it
- Use a supported context in the appropriate field.
- Prevent it next time
- Annotate when each value becomes available.
The text false enables a branch intended for boolean truth.
- Why it happens
- A string output was treated as a typed boolean.
- How to fix it
- Compare or parse the output explicitly under Actions expression rules.
- Prevent it next time
- Define output types and test empty, false, and malformed cases.
Quoting YAML is assumed to prevent shell injection.
- Why it happens
- Expression substitution occurs before the shell interprets the resulting script.
- How to fix it
- Pass untrusted values as data through a reviewed environment or structured interface.
- Prevent it next time
- Review all three interpretation layers.
Check your understanding
1. A step writes an output called ready. Can an earlier job-level if read that same step output?
Show worked solution
No. The step has not executed when the job is being selected. Move the decision to an appropriate later step or declare a producer job output and consume it through needs. Respect context availability rather than rearranging quotes.
2. Why is the Python example explicitly not an Actions expression emulator?
Show worked solution
Languages have different coercion and equality rules. The program demonstrates that JSON can represent a boolean distinct from its source text. Actions conditions must be reviewed against GitHub’s documented expression semantics and tested in an authorized lab if platform behavior matters.
Key takeaways
- Track YAML, Actions expressions, and shell parsing separately.
- Validate type, availability, and missing-data behavior before using a condition.