Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

421 lines · 24.9 KBRaw History
1---
2title: Expressions and contexts
3description: The ${{ }} expression language in Diffinery workflows: operators, functions, every context property Diffinery sets, and the default environment variables.
4---
5
6Expressions let a workflow compute values and conditions at run time: `${{ github.ref_name }}`, `if: failure()`, `${{ fromJSON(needs.plan.outputs.matrix) }}`. Diffinery implements the GitHub Actions expression language, with the same syntax, operators, functions and type rules, and fills in the same contexts. This page is the reference for all of it, including the default environment variables every step gets and every difference from GitHub.
7
8## Syntax
9
10An expression is written between `${{` and `}}`:
11
12```yaml
13env:
14 IMAGE: diffinery.com/acme/website:${{ github.sha }}
15steps:
16 - run: echo "Building ${{ github.ref_name }} for ${{ github.actor }}"
17```
18
19- In most values, each `${{ }}` is replaced by its result converted to a string, and the text around it is kept.
20- When a value is exactly one expression (for example `matrix: ${{ fromJSON(needs.plan.outputs.matrix) }}`), the result keeps its type: a list stays a list, a number stays a number.
21- In `if:` conditions of jobs and steps, `${{ }}` is optional: `if: github.event_name == 'push'` is the same as `if: ${{ github.event_name == 'push' }}`.
22- `}}` inside a single-quoted string doesn't end the expression. To write a literal `${{`, use `${{ '${{' }}`.
23- Expressions in `on:` are not evaluated.
24
25Every expression in a file is checked for syntax when the file is parsed. A mistake fails the file with `Invalid expression ${{ <expr> }}: <detail>` (or `Invalid expression: <detail>` in an `if:` without `${{ }}`), and an unclosed one with `Unclosed expression: '${{' at offset <n> has no matching '}}'`. Run `diffinery workflow validate` to catch these before you push.
26
27> **Note:** In an `if:` that mixes text and expressions, such as `if: ${{ a }} && ${{ b }}`, the result is a string, which is true unless it's empty. Write the whole condition as one expression instead: `if: a && b`. GitHub behaves the same way.
28
29## Literals
30
31| Type | Examples | Notes |
32| --- | --- | --- |
33| null | `null` | |
34| Boolean | `true`, `false` | |
35| Number | `42`, `-1`, `3.14`, `2e3`, `0xff`, `0o17`, `NaN`, `Infinity` | Any JSON number, plus hexadecimal (`0x`) and octal (`0o`). A leading `-` or `+` is part of the number: there is no arithmetic and no unary minus. |
36| String | `'hello'`, `'it''s'` | Single quotes only; write `''` for a quote. Double quotes fail with `Unexpected double quote; string literals use single quotes`. |
37
38## Operators
39
40| Operator | Meaning |
41| --- | --- |
42| `( )` | Grouping |
43| `[ ]` | Index: `github.event['pull_request']`, `matrix.include[0]` |
44| `.` | Property: `github.event.pull_request.title` |
45| `.*`, `[*]` | Object filter (see below) |
46| `!` | Not |
47| `<`, `<=`, `>`, `>=` | Comparison |
48| `==`, `!=` | Equality |
49| `&&` | And |
50
51The or operator is `||`. Precedence, from highest to lowest: property access and indexing, `!`, comparisons, equality, `&&`, `||`. There are no arithmetic operators, as on GitHub.
52
53`&&` and `||` return one of their operands, not a boolean: `a && b` is `a` when `a` is falsy and `b` otherwise; `a || b` is `a` when `a` is truthy and `b` otherwise. That gives you defaults: `${{ inputs.tag || 'latest' }}`.
54
55### Property access
56
57- Property names are case-insensitive: `github.EVENT_NAME` reads `github.event_name`.
58- Names may contain `-`: `steps.build-app.outputs.path`, `matrix.node-version`.
59- Reading a property of something that doesn't have it gives `null` instead of an error, so `github.event.pull_request.title` is `null` on a push. An unknown context name is `null` too.
60- Array indexes start at 0. An index out of range gives `null`.
61
62### Object filters
63
64`.*` collects a property from every item of an array or object and gives a list:
65
66```yaml
67- run: echo "${{ toJSON(github.event.pull_request.labels.*.name) }}"
68- if: contains(github.event.pull_request.labels.*.name, 'deploy')
69 run: ./deploy.sh
70```
71
72### Types and conversion
73
74Comparisons convert values the way GitHub does:
75
76| Rule | Example |
77| --- | --- |
78| Values of different types are compared as numbers. `null` is `0`, `true` is `1`, `false` is `0`, `''` is `0`, numeric strings are their number, and anything else is `NaN`. | `'1' == 1` is true, `null == 0` is true |
79| Strings are compared case-insensitively. | `'Main' == 'main'` is true |
80| A comparison with `NaN` is false. | `'abc' < 1` is false |
81| Arrays and objects are only equal to themselves. | `fromJSON('[1]') == fromJSON('[1]')` is false |
82
83Truthiness: `false`, `0`, `-0`, `NaN`, `''` and `null` are falsy; everything else, including every array and object, is truthy.
84
85When a result is inserted into text: `null` becomes `''`, booleans become `true` or `false`, whole numbers have no decimals, and arrays and objects become JSON (indented by 2 spaces).
86
87## Functions
88
89Function names are case-insensitive. Calling an unknown function fails with `Unrecognized function '<name>'`, and a wrong number of arguments with `Function '<name>' expects <n> argument(s) but got <m>`.
90
91| Function | Returns |
92| --- | --- |
93| `contains(search, item)` | If `search` is an array: `true` when an element equals `item` (with the equality rules above). Otherwise `true` when the string `search` contains `item`, ignoring case. |
94| `startsWith(string, prefix)` | `true` when `string` starts with `prefix`, ignoring case. |
95| `endsWith(string, suffix)` | `true` when `string` ends with `suffix`, ignoring case. |
96| `format(string, value0, value1, ...)` | `string` with `{0}`, `{1}`, ... replaced by the values. Write `{{` and `}}` for literal braces. |
97| `join(array, separator)` | The elements joined with `separator` (default `,`). A non-array is converted to a string. |
98| `toJSON(value)` | `value` as JSON, indented by 2 spaces. |
99| `fromJSON(string)` | The parsed value. Use it to turn strings into booleans, numbers, lists or objects. |
100| `hashFiles(pattern, ...)` | A SHA-256 hash of the matching files, or `''` when none match (see below). |
101| `success()`, `failure()`, `cancelled()`, `always()` | Status checks (see below). |
102
103Examples:
104
105```yaml
106- run: echo "${{ format('{0}/{1}@{2}', github.repository_owner, github.event.repository.name, github.sha) }}"
107- if: startsWith(github.ref, 'refs/tags/v')
108 run: ./release.sh
109- uses: actions/cache@v4
110 with:
111 path: ~/.npm
112 key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
113```
114
115Runtime errors from functions fail the step or job that evaluates them:
116
117| Message | Cause |
118| --- | --- |
119| `fromJSON: invalid JSON "<text>": <detail>` | The argument isn't valid JSON. |
120| `format: invalid format string '<string>'` | A `{` isn't followed by a number and `}`. |
121| `format: invalid format string '<string>' (unescaped '}')` | A lone `}`. Write `}}`. |
122| `format: placeholder {<n>} has no matching argument in '<string>'` | Not enough values. |
123
124### hashFiles
125
126`hashFiles` takes one or more glob patterns relative to `GITHUB_WORKSPACE` (`*`, `**`, `?`, `[...]`, and `!pattern` to exclude). It hashes each matching file with SHA-256, then hashes those hashes together, and returns the hex digest. Files outside the workspace and `.git` directories are ignored. It returns `''` when no file matches.
127
128`hashFiles` only works in places evaluated on the runner, that is in steps, job `env`, `container`, `services` and `outputs`. Elsewhere (job `if`, `runs-on`, `strategy`, `name`) it returns `''`.
129
130### Status check functions
131
132In a job's `if:`, the functions look at all of the job's ancestors: the jobs in its `needs`, the jobs those need, and so on up the chain, as on GitHub.
133
134| Function | True when |
135| --- | --- |
136| `success()` | The run hasn't been cancelled and every ancestor succeeded. A skipped or cancelled ancestor makes it false. |
137| `failure()` | An ancestor failed or timed out. |
138| `cancelled()` | The run has been cancelled. |
139| `always()` | Always. |
140
141A matrix job's result combines its legs: any failed leg fails it, otherwise any cancelled leg cancels it, and it's skipped when every leg was skipped. A leg that failed with `continue-on-error: true` counts as a success. A reusable workflow call's result combines the jobs inside it the same way.
142
143In a step's `if:`:
144
145| Function | True when |
146| --- | --- |
147| `success()` | No earlier step of the job failed (steps with `continue-on-error: true` don't count) and the job isn't being cancelled or timing out. |
148| `failure()` | An earlier step failed. |
149| `cancelled()` | The job is being cancelled or has hit its timeout. |
150| `always()` | Always. |
151
152An `if:` that doesn't call any of these is treated as `success() && (<condition>)`. So `if: github.ref == 'refs/heads/main'` is skipped after a failure, while `if: always() && github.ref == 'refs/heads/main'` still runs. Inside a composite action, the functions look at the action's own steps.
153
154```yaml
155jobs:
156 test:
157 runs-on: ubuntu-latest
158 steps:
159 - run: npm test
160 - if: failure()
161 run: echo "::error::Tests failed"
162 notify:
163 needs: test
164 if: always()
165 runs-on: ubuntu-latest
166 steps:
167 - run: echo "test finished with ${{ needs.test.result }}"
168```
169
170Because a job's default `if:` is `success()`, a skipped job skips every job that depends on it, directly or further down the chain. To run a job anyway, give it a status function, for example `if: always()` or `if: ${{ !cancelled() && needs.build.result != 'failure' }}`:
171
172```yaml
173jobs:
174 build:
175 if: github.event_name == 'push'
176 runs-on: ubuntu-latest
177 steps:
178 - run: make
179 test:
180 needs: build # skipped when build is skipped
181 runs-on: ubuntu-latest
182 steps:
183 - run: make test
184 report:
185 needs: test
186 if: ${{ !cancelled() }} # runs even when build and test were skipped
187 runs-on: ubuntu-latest
188 steps:
189 - run: echo "test result was ${{ needs.test.result }}"
190```
191
192## Contexts
193
194### Where each context is available
195
196Some values are evaluated by Diffinery before a job starts, and the rest by the runner while the job runs, so not every context is available everywhere:
197
198| Where | Available contexts |
199| --- | --- |
200| `run-name`, top-level `concurrency` | `github`, `inputs`, `vars` |
201| `jobs.<id>.if` | `github`, `needs`, `inputs`, `vars`, `env` (workflow-level values, unevaluated), status functions |
202| `jobs.<id>.strategy` | `github`, `needs`, `inputs`, `vars`, `env` |
203| `jobs.<id>.name`, `jobs.<id>.runs-on`, `jobs.<id>.timeout-minutes`, `jobs.<id>.continue-on-error`, `jobs.<id>.concurrency` | `github`, `needs`, `inputs`, `vars`, `env`, `matrix`, `strategy`. These are evaluated for each matrix combination. |
204| `jobs.<id>.environment` (`name` and `url`) | `github`, `needs`, `inputs`, `vars`, `matrix`, `strategy`. A `url` that refers to `steps.` is ignored. |
205| `jobs.<id>.with` (reusable workflow call) | `github`, `needs`, `inputs`, `vars` |
206| `jobs.<id>.secrets` (reusable workflow call) | `github`, `needs`, `inputs`, `vars`, `secrets` |
207| `jobs.<id>.env`, `container`, `services` | `github`, `needs`, `inputs`, `vars`, `secrets`, `matrix`, `strategy`, `runner`, `job` |
208| Steps: `if`, `name`, `uses`, `run`, `with`, `env`, `shell`, `working-directory`, `continue-on-error`, `timeout-minutes` | `github`, `env`, `vars`, `secrets`, `matrix`, `needs`, `steps`, `runner`, `inputs`, `job`, `strategy` |
209| `jobs.<id>.outputs` | Same as steps |
210| `on.workflow_call.outputs` | `github`, `jobs`, `inputs`, `vars` |
211
212In places evaluated before the job starts, `github.token` is empty and `github.workspace` isn't set, and `vars` and `secrets` hold only repository (and organization) values: a job's [environment](/docs/actions/workflow-syntax#environment) variables and secrets are added when the job starts, so they're available in steps, `jobs.<id>.env`, `container`, `services` and `outputs`, but not in `jobs.<id>.if`, `runs-on`, `name` or `environment` itself.
213
214### github
215
216Information about the run and the event. Most properties are also environment variables (see [Default environment variables](#default-environment-variables)).
217
218| Property | Value |
219| --- | --- |
220| `github.event_name` | The event that started the run, for example `push`, `pull_request`, `release` or `repository_dispatch` |
221| `github.event` | The event payload. See [Events that trigger workflows](/docs/actions/events) for each event's fields, for example `github.event.release.tag_name` for `release` and `github.event.client_payload` for `repository_dispatch`. |
222| `github.sha` | The commit the run checks out. See the table in [Events that trigger workflows](/docs/actions/events#github_sha-and-github_ref-by-event). |
223| `github.ref` | The full ref, for example `refs/heads/main`, `refs/tags/v1.2.0` (also for `release` runs) or `refs/pull/7/merge` |
224| `github.ref_name` | The short ref: `main`, `v1.2.0`, or `7/merge` for pull requests |
225| `github.ref_type` | `tag` for tags, `branch` otherwise |
226| `github.ref_protected` | Always `false` |
227| `github.head_ref` | The pull request's head branch for `pull_request` and `pull_request_target`, otherwise `''` |
228| `github.base_ref` | The pull request's base branch for `pull_request` and `pull_request_target`, otherwise `''` |
229| `github.repository` | `acme/website` |
230| `github.repository_id` | The repository's ID |
231| `github.repository_owner` | `acme` |
232| `github.repositoryUrl` | `https://diffinery.com/acme/website.git` |
233| `github.actor` | The login of the account that triggered the run (for a re-run, who re-ran it): for `release`, whoever published, edited or deleted the release; for `repository_dispatch`, the account whose token sent the event. Scheduled runs use `diffinery-scheduler`; merge queue groups use the login of whoever queued the pull request. |
234| `github.triggering_actor` | Same as `github.actor` |
235| `github.run_id` | The run number, as a string, for example `128`. It stays the same on a re-run, and the REST API accepts it wherever a run ID goes, so actions that parse it as a number work. |
236| `github.run_number` | The run's number, as a string |
237| `github.run_attempt` | `1`, or higher for re-runs, as a string |
238| `github.workflow` | The workflow's `name`, or its path when it has none |
239| `github.workflow_ref` | `acme/website/.github/workflows/ci.yml@refs/heads/main` |
240| `github.job` | The job's ID (inside a reusable workflow, its ID in that workflow) |
241| `github.server_url` | `https://diffinery.com` |
242| `github.api_url` | `https://diffinery.com/api/v3` |
243| `github.graphql_url` | `https://diffinery.com/api/graphql`. Set for compatibility only: Diffinery has no GraphQL API yet |
244| `github.token` | The job's token (same as `secrets.GITHUB_TOKEN`) |
245| `github.workspace` | The workspace directory, `GITHUB_WORKSPACE` |
246| `github.retention_days` | `90` |
247| `github.secret_source` | `Actions` |
248| `github.action` | `''` |
249| `github.action_path` | Inside a composite action: the action's directory. Otherwise `''`. |
250| `github.action_repository`, `github.action_ref` | Inside a composite action: the action's repository and ref. |
251
252GitHub properties Diffinery doesn't set include `github.actor_id`, `github.repository_owner_id`, `github.workflow_sha`, `github.event_path` (use the `GITHUB_EVENT_PATH` variable), `github.env`, `github.path`, `github.output`, `github.state` and `github.step_summary` (use the environment variables of the same names).
253
254> **Note:** `github.run_number` counts runs of all workflows in the repository, not each workflow separately as on GitHub. Because `github.run_id` is the run number, the two are always equal.
255
256### env
257
258Environment variables set in the workflow: the workflow's `env`, the job's `env`, the step's `env` and anything earlier steps wrote to `$GITHUB_ENV`. It doesn't include the default variables such as `GITHUB_SHA`; read those with `github.*` or in the shell.
259
260```yaml
261env:
262 STAGE: production
263jobs:
264 deploy:
265 runs-on: ubuntu-latest
266 steps:
267 - if: env.STAGE == 'production'
268 run: echo "Deploying to $STAGE"
269```
270
271### vars
272
273The repository's variables, set under **Settings** > **Secrets and variables** or with `PUT /repos/{owner}/{repo}/actions/variables/{name}`. In a job with `environment:`, the environment's variables are added once the job starts and win over repository variables with the same name. Names are stored in upper case, and lookups ignore case. An unset variable is `''`. See [Secrets and variables](/docs/actions/secrets-and-variables) and [Environments](/docs/actions/environments).
274
275### secrets
276
277| Property | Value |
278| --- | --- |
279| `secrets.<NAME>` | A repository secret, or an organization secret shared with the repository (by its `visibility`; see [Organization secrets](/docs/actions/secrets-and-variables#organization-secrets)). A repository secret wins over an organization secret with the same name. In a job with `environment:`, the environment's secrets win over both (pull request runs, and some runs whose code came from an agent, don't get environment secrets; see [environment](/docs/actions/workflow-syntax#environment)). |
280| `secrets.GITHUB_TOKEN` | The job's token. What it may do follows the job's `permissions:`; see [GITHUB_TOKEN permissions](/docs/actions/workflow-permissions). |
281| `secrets.DIFFINERY_TOKEN` | The same job token, under a Diffinery name. |
282
283Secret names are stored in upper case and lookups ignore case; an unset secret is `''`. Inside a reusable workflow, `secrets` holds only what the caller passed (or everything with `secrets: inherit`), plus the two tokens. Secret values are masked in logs.
284
285### matrix
286
287The values of the current matrix combination, for example `matrix.os` and `matrix.node`. Empty for jobs without a matrix.
288
289### strategy
290
291| Property | Value |
292| --- | --- |
293| `strategy.fail-fast` | Whether `fail-fast` is on |
294| `strategy.job-index` | This job's position in the matrix, from 0 |
295| `strategy.job-total` | How many jobs the matrix produced |
296| `strategy.max-parallel` | The effective `max-parallel` |
297
298### needs
299
300For each job in the job's `needs`:
301
302| Property | Value |
303| --- | --- |
304| `needs.<job_id>.result` | `success`, `failure`, `cancelled` or `skipped`. For a matrix job: `failure` if any of its jobs failed or timed out, otherwise `cancelled` if any was cancelled, `skipped` if all were skipped, otherwise `success`. |
305| `needs.<job_id>.outputs.<name>` | The job's outputs (strings). For a matrix job, the merged outputs of all its jobs. For a reusable workflow call, the called workflow's `on.workflow_call.outputs`. |
306
307### steps
308
309For each earlier step with an `id` in the same job (or the same composite action):
310
311| Property | Value |
312| --- | --- |
313| `steps.<id>.outputs.<name>` | Outputs the step set with `$GITHUB_OUTPUT` or `::set-output` |
314| `steps.<id>.outcome` | `success`, `failure`, `cancelled` or `skipped`, before `continue-on-error` |
315| `steps.<id>.conclusion` | The same, after `continue-on-error` (a failed step with `continue-on-error: true` is `success`) |
316
317### runner
318
319| Property | Value |
320| --- | --- |
321| `runner.os` | `Linux` |
322| `runner.arch` | `X64` on hosted runners (`ARM64`, `ARM` or `X86` on self-hosted machines with those processors) |
323| `runner.name` | `diffinery` |
324| `runner.environment` | `diffinery-hosted` |
325| `runner.temp` | A temporary directory for the job; `/workspace/_temp` on hosted runners, where every job starts in a fresh container |
326| `runner.tool_cache` | Where setup actions install tools; `/opt/hostedtoolcache` on hosted runners |
327| `runner.workspace` | The parent of `GITHUB_WORKSPACE`; `/workspace` on hosted runners |
328| `runner.debug` | `1` when debug logging is on (secret or variable `ACTIONS_STEP_DEBUG` set to `true`), otherwise `''`. `ACTIONS_RUNNER_DEBUG` doesn't change it. |
329
330### job
331
332| Property | Value |
333| --- | --- |
334| `job.status` | `success`, `failure` or `cancelled`: the job's state so far |
335| `job.container.id`, `job.container.network` | The job container's ID and network, when the job has a `container` |
336| `job.services.<name>.id`, `.network`, `.ports` | Each service container's ID, network and port mappings (`ports['5432']` is the host port) |
337
338### inputs
339
340For `workflow_dispatch` runs: the dispatch inputs, typed (booleans are booleans, numbers are numbers). Inside a reusable workflow: the inputs the caller passed with `with`, plus declared defaults. For other events: empty.
341
342### jobs
343
344Only in `on.workflow_call.outputs` of a reusable workflow: `jobs.<job_id>.result` and `jobs.<job_id>.outputs.<name>` for the called workflow's jobs.
345
346## Default environment variables
347
348Every step gets these variables. Workflows can't override the `GITHUB_*` and `RUNNER_*` ones; setting them in `env` has no effect. Paths are those of hosted runners, where the repository `acme/website` is checked out at `/workspace/website`.
349
350| Variable | Value |
351| --- | --- |
352| `CI` | `true` |
353| `GITHUB_ACTIONS` | `true` |
354| `DIFFINERY` | `true`. Use it to detect Diffinery in scripts. |
355| `GITHUB_REPOSITORY` | `acme/website` |
356| `GITHUB_REPOSITORY_OWNER` | `acme` |
357| `GITHUB_SHA` | The commit the run is about (see [Events that trigger workflows](/docs/actions/events#github_sha-and-github_ref-by-event)) |
358| `GITHUB_REF` | The full ref |
359| `GITHUB_REF_NAME` | The short ref (`main`, `v1.2.0`, `7/merge`) |
360| `GITHUB_REF_TYPE` | `branch` or `tag` |
361| `GITHUB_HEAD_REF` | The head branch for pull request events, otherwise empty |
362| `GITHUB_BASE_REF` | The base branch for pull request events, otherwise empty |
363| `GITHUB_RUN_ID` | The run number, as in `github.run_id` |
364| `GITHUB_RUN_NUMBER` | The run's number |
365| `GITHUB_RUN_ATTEMPT` | The attempt number |
366| `GITHUB_JOB` | The job's ID |
367| `GITHUB_ACTOR` | Who triggered the run |
368| `GITHUB_TRIGGERING_ACTOR` | Same as `GITHUB_ACTOR` |
369| `GITHUB_EVENT_NAME` | The event name |
370| `GITHUB_EVENT_PATH` | A JSON file holding the event payload, `/workspace/_temp/_github_workflow/event.json` |
371| `GITHUB_SERVER_URL` | `https://diffinery.com` |
372| `GITHUB_API_URL` | `https://diffinery.com/api/v3` |
373| `GITHUB_GRAPHQL_URL` | `https://diffinery.com/api/graphql`. Set for compatibility only: Diffinery has no GraphQL API yet |
374| `GITHUB_WORKFLOW` | The workflow's name, or its path |
375| `GITHUB_WORKFLOW_REF` | `acme/website/.github/workflows/ci.yml@refs/heads/main` |
376| `GITHUB_WORKSPACE` | `/workspace/website` |
377| `GITHUB_RETENTION_DAYS` | `90` |
378| `GITHUB_ACTION` | The step's `id`, or `__run` for a `run` step without one (for actions, `__` plus the action's repository with other characters replaced by `_`) |
379| `GITHUB_ENV`, `GITHUB_PATH`, `GITHUB_OUTPUT`, `GITHUB_STATE`, `GITHUB_STEP_SUMMARY` | Paths of the step's [environment files](/docs/actions/workflow-syntax#environment-files) |
380| `RUNNER_OS` | `Linux` |
381| `RUNNER_ARCH` | `X64` on hosted runners |
382| `RUNNER_NAME` | `diffinery` |
383| `RUNNER_ENVIRONMENT` | `diffinery-hosted` |
384| `RUNNER_TEMP` | `/workspace/_temp` |
385| `RUNNER_TOOL_CACHE` | `/opt/hostedtoolcache` |
386| `RUNNER_WORKSPACE` | `/workspace` |
387| `AGENT_TOOLSDIRECTORY` | Same as `RUNNER_TOOL_CACHE` |
388| `ImageOS` | `ubuntu24`, as on GitHub's `ubuntu-24.04` image |
389| `ImageVersion` | `20250101.1` |
390
391Set only in some cases:
392
393| Variable | When |
394| --- | --- |
395| `RUNNER_DEBUG` | `1` when the secret or variable `ACTIONS_STEP_DEBUG` or `ACTIONS_RUNNER_DEBUG` is `true` |
396| `ACTIONS_RUNTIME_TOKEN`, `ACTIONS_RUNTIME_URL`, `ACTIONS_RESULTS_URL`, `ACTIONS_CACHE_URL`, `ACTIONS_CACHE_SERVICE_V2` | In JavaScript actions, Docker container actions and the built-in actions only, never in `run:` steps. They give access to the job's cache and artifact services and stop working when the job ends. See [Caching and artifacts](/docs/actions/caching-and-artifacts#the-runtime-services). |
397| `ACTIONS_ID_TOKEN_REQUEST_URL`, `ACTIONS_ID_TOKEN_REQUEST_TOKEN` | When the job has `permissions: id-token: write`. Used by OIDC clients; see [OIDC](/docs/actions/oidc). |
398| `GITHUB_ACTION_PATH`, `GITHUB_ACTION_REPOSITORY`, `GITHUB_ACTION_REF` | In JavaScript and Docker actions, and in the steps of composite actions |
399| `INPUT_<NAME>` | In JavaScript and Docker actions: each input, with the name upper-cased and spaces replaced by `_` |
400| `STATE_<NAME>` | In an action's main and post scripts: values saved with `$GITHUB_STATE` or `::save-state` |
401
402Hosted runners also set `LANG=C.UTF-8`, `LC_ALL=C.UTF-8`, `PIP_BREAK_SYSTEM_PACKAGES=1` (so `pip install` works with Ubuntu's Python) and `COREPACK_ENABLE_DOWNLOAD_PROMPT=0`.
403
404GitHub variables that Diffinery doesn't set include `GITHUB_ACTOR_ID`, `GITHUB_REPOSITORY_ID`, `GITHUB_REPOSITORY_OWNER_ID`, `GITHUB_WORKFLOW_SHA` and `GITHUB_REF_PROTECTED`. `GITHUB_TOKEN` isn't an environment variable unless you set it, as on GitHub:
405
406```yaml
407- run: gh api repos/${{ github.repository }}
408 env:
409 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
410```
411
412## Differences from GitHub
413
414- `jobs.<id>.environment.url` can't use step outputs.
415- Job-level `if:` can also read the workflow-level `env`.
416- `hashFiles()` returns `''` outside steps and other runner-evaluated values.
417- `github.event.inputs` keeps the types of dispatch inputs; on GitHub its values are strings.
418- `github.run_number` counts runs across all workflows of the repository, and `github.run_id` is the same number.
419- On a re-run, `github.actor` is whoever started the re-run (and `github.triggering_actor` matches it).
420- `github.ref_protected` is always `false`, and `secrets.DIFFINERY_TOKEN` is an extra name for the job token.
421- `GITHUB_*` and `RUNNER_*` defaults can't be overridden, but `CI` and `DIFFINERY` can.