Triggers, expressions, and contexts¶
Event configuration and the expression and context model. For the security consequences of
privileged triggers, see security.md.
Choosing a trigger¶
| Trigger | Typical use | Trust note |
|---|---|---|
push |
CI on branches and tags | Trusted authors; token has write by default |
pull_request |
CI for PRs, including forks | Fork runs get a read-only token and no secrets |
pull_request_target |
Label or comment on fork PRs without running their code | Privileged: base context, secrets, write token |
workflow_dispatch |
Manual run with inputs | Workflow must exist on the default branch |
schedule |
Periodic jobs | Runs the default branch only |
workflow_run |
Privileged follow-up to an unprivileged workflow | Privileged; treat upstream artifacts as untrusted |
repository_dispatch |
External system triggers via API | Default branch context |
merge_group |
Merge queue validation | Add alongside pull_request when a merge queue is enabled |
issues, issue_comment |
Triage and bots | Privileged; untrusted input |
Prefer pull_request for CI. Use pull_request_target only for metadata operations (labels,
comments) that do not execute contributor code. See security.md for the pwn-request pattern.
Event details¶
pull_request:
- Default activity types are
opened,synchronize,reopened. Detecting a merge needstypes: [closed]plusgithub.event.pull_request.merged == true. branchesfilters match the base branch;pathsfilters match the diff. If both are set, both must match.- The payload is empty for fork PRs and merged PRs; use
github.event.pull_request.head.shafor the head commit.
workflow_dispatch:
- Inputs are typed:
string,choice,number,boolean,environment. inputs.<name>preserves the type;github.event.inputs.<name>is always a string. Preferinputs.- The workflow file must be on the default branch to appear in the UI. After one run, any
branch or tag can be dispatched through the API or
gh workflow run.
schedule:
- Five-field POSIX cron, UTC by default, minimum interval 5 minutes.
- Runs the latest commit of the default branch only.
- Delays are normal under load;
@dailystyle macros are not supported. - Public repositories disable scheduled workflows after 60 days of inactivity.
workflow_run:
- Must be defined on the default branch; the run uses the default branch ref and sha.
- Can chain at most 3 levels. Filter with
workflows: [name]andtypes: [completed]. - Gets secrets and a write token even when the triggering workflow did not. That is the privilege-separation tool, and the reason its inputs must be treated as untrusted.
push:
branchesandbranches-ignorecannot be combined; use ordered!patterns instead.- If only tag filters exist, branch pushes do not trigger. If neither exists, both trigger.
- Path filters are not evaluated for tag pushes.
- Path filters are skipped when a push contains more than 1,000 commits. When a diff has more than 3,000 files and no match is in the first 3,000, the workflow does not run.
merge_group:
- When required checks are enforced through a merge queue, add
merge_groupor merges block waiting for checks that never run.
Expressions¶
- Literals: booleans,
null, numbers, strings. Use single quotes; double quotes are invalid. Escape a quote by doubling it ('It''s'). - Falsy values:
false,0,-0,"",null. Everything else is truthy. - Operators:
!,==,!=,<,<=,>,>=,&&,||,[],.,(). There is no arithmetic and no ternary. - String comparison is case-insensitive; mismatched types coerce to numbers.
- Functions:
contains,startsWith,endsWith,format,join,toJSON,fromJSON,hashFiles, plus the status functions.formatuses{0}placeholders and{{and}}escapes. fromJSONturns an output string into a number, array, or object, for example a dynamic matrix.hashFiles(glob)returns a SHA-256 over matching files relative to the workspace and an empty string when nothing matches. It is only allowed in step-level keys.- Status functions (
success(),failure(),cancelled(),always()) are only valid injobs.<id>.ifandsteps.if. Elsewhere usejob.status.
Contexts¶
Available contexts: github, env, vars, job, jobs (reusable workflows only),
steps, runner, secrets, strategy, matrix, needs, inputs.
Availability depends on the workflow key. The restrictions that cause most errors:
| Key | Allowed contexts |
|---|---|
run-name, concurrency |
github, inputs, vars |
workflow env |
github, secrets, inputs, vars |
jobs.<id>.if |
github, needs, vars, inputs |
jobs.<id>.runs-on, .name, .environment, .timeout-minutes, .strategy |
github, needs, strategy, matrix, vars, inputs |
jobs.<id>.env |
github, needs, strategy, matrix, vars, secrets, inputs |
steps.if |
github, needs, strategy, matrix, job, runner, env, vars, steps, inputs (no secrets) |
steps.run, .env, .with, .name, .working-directory |
the step if set plus secrets |
github context notes:
github.eventis the raw webhook payload and differs per event; branch ongithub.event_name.github.refis fully formed (refs/heads/main);github.ref_nameis short. For unmerged PRsgithub.refisrefs/pull/N/merge.github.shadepends on the event; forpull_requestit is the merge commit, not the head.github.actoris the user who started the run; a re-run uses the original actor's privileges.github.triggering_actoris who triggered the current attempt.github.tokenis null outside step execution;secrets.GITHUB_TOKENworks in job-levelenvandwith.github.base_refandgithub.head_refexist only for pull request events.
Variables¶
Three separate mechanisms:
| Mechanism | Scope | Notes |
|---|---|---|
env |
workflow, job, step | defined in YAML; not secret |
vars |
organization, repository, environment | configured in GitHub settings; unset is an empty string |
inputs |
workflow_dispatch, workflow_call |
typed; only for callable workflows |
Configuration variable precedence: environment > repository > organization. Default
environment variables such as GITHUB_* and RUNNER_* are provided by the runner and cannot
be overwritten. CI is always true.
When debugging, print context safely with ${{ toJSON(github) }}; never dump secrets or
github.token.