Skip to content
2,067 linesCodeBlameRaw
1---
2title: GitHub Actions
3description: Your GitHub Actions workflows run on g1t as they are. Rename .github to .g1t and push.
4---
5
6g1t runs GitHub Actions workflows. They are written exactly as on GitHub,
7and kept in `.g1t/workflows/` instead of `.github/workflows/`.
8
9## Moving from GitHub
10
11```sh
12git mv .github .g1t
13git commit -m "Run our workflows on g1t"
14git push g1t main
15```
16
17That is the whole move. Everything in the folder comes along: workflows,
18local actions under `.g1t/actions/` and anything else you keep there.
19Workflows that still say `uses: ./.github/actions/setup` find it under
20`.g1t/` once `.github` is gone.
21
22Otherwise g1t doesn't read `.github`, so a repository that lives in both
23places can keep `.github` for GitHub and `.g1t` for g1t, side by side.
24
25### On a mirror
26
27A [mirror](/guides/mirroring/) of a GitHub repository runs nothing while it
28stands by: its workflows run on GitHub. Its owners can change that:
29
30- **CI failover** runs its workflows on g1t for each push copied in from
31 GitHub, `.github/workflows` as well as `.g1t/workflows`. Use it when
32 GitHub's workflows aren't running.
33- **Taking over** runs them for everything pushed to g1t, until it's handed
34 back.
35- **Keep CI warm** runs `.g1t/workflows`, and only those, on each push
36 copied in while it stands by.
37
38When both folders have a workflow of the same `name:`, the one in `.g1t`
39runs. A workflow that deploys (any job names an `environment:`) waits for
40approval during CI failover and takeovers, so it doesn't deploy from two
41places, unless the mirror's settings say otherwise. A push copied in from
42GitHub that changes workflows or local actions waits for approval before
43it can use the repository's secrets.
44
45Whenever g1t runs a mirror's workflows, their results show on the commit on
46GitHub too, as commit statuses named `g1t / <workflow> / <event>`, such as
47`g1t / CI / push`. This needs the Commit statuses permission on g1t's GitHub
48App; turn it off with **Report checks to GitHub** in the mirror's settings.
49See [Checks on GitHub](/guides/mirroring/#checks-on-github).
50
51Then add your [secrets and variables](#secrets-and-variables): GitHub never
52gives their values out, so they cannot be copied across.
53
54## What runs
55
56| On GitHub | On g1t |
57| --- | --- |
58| `on:` `push` (branches, tags, paths), `pull_request`, `pull_request_target`, `issues`, `issue_comment`, `pull_request_review`, `schedule`, `workflow_dispatch`, `workflow_run`, `merge_group`, `create`, `delete`, `repository_dispatch`, `release`, `deployment`, `deployment_status` | The same, from g1t's own pushes, pull requests, issues, comments, [releases](#releases), [deployments](#deployments) and [merge queue](/guides/merge-queue/). `create` starts on each new branch or tag, at its commit; `delete` on each branch or tag a push deletes, on the default branch's latest commit, with `github.event.ref` and `github.event.ref_type` naming what went. As on GitHub, neither takes `branches` or `tags` filters: a workflow on them runs for every branch and tag. `repository_dispatch` starts on [a dispatch event](#repository-dispatch). |
59| `jobs`, `needs`, `if`, `outputs`, `env`, `defaults`, `timeout-minutes`, `continue-on-error` | The same. |
60| `timeout-minutes` and `continue-on-error` on a step | The same, for `run:` and `uses:` steps alike. A `uses:` step's action is stopped at its limit, with every process it started; a step inside a composite action stops at its own limit or the `uses:` step's, whichever comes first. A step stopped this way fails, unless `continue-on-error` lets the job go on. |
61| `strategy.matrix` with `include` and `exclude`, `fail-fast`, `max-parallel`, a matrix from `fromJSON(needs.…)` | The same. |
62| `concurrency` with `cancel-in-progress`, for the workflow or for one job | The same: one run, or one job, of a group at a time. |
63| `permissions:` for the workflow or for one job, `read-all`, `write-all` | The same: they decide what [the job's token](#the-jobs-token) may do. |
64| `${{ }}` expressions: every operator, function and context | The same, including `hashFiles`, and `success()`, `failure()`, `always()` and `cancelled()` in `if:` (anywhere else they are an error, as on GitHub). |
65| `run:` with `bash`, `sh`, `python`, `pwsh` or a custom shell | The same, with GitHub's file for each: `.sh` for `bash` and `sh`, `.py` for `python`, `.ps1` for PowerShell (stopping at the first error, and exiting with the last program's code), `.cmd` for `cmd`, and no extension for another program. `python` runs `python`, or `python3` on a machine without it. |
66| JavaScript actions (`uses: owner/repo@v7`, `owner/repo/path@v7`) | Fetched from that repository on g1t when g1t has it and your repository may use it, otherwise from GitHub, and run as they are, on Node 24, the runtime current actions declare. An action that declares `node20` runs on Node 20 where the machine has one. See [actions and workflows from other repositories](#actions-and-workflows-from-other-repositories) and [an action's pre and post](#an-actions-pre-and-post). |
67| Composite actions | The same. |
68| Reusable workflows (`jobs.<id>.uses: ./.g1t/workflows/build.yml`, or `owner/repo/.g1t/workflows/build.yml@v1` in another repository) | The same: `with:` inputs checked against their `type`, `secrets:` by name or `secrets: inherit`, `on.workflow_call` outputs, `strategy.matrix` on the calling job (one call for each combination), `concurrency` on the calling job, and up to ten levels of workflows with at most 50 different reusable workflows in a run. `.github/workflows/…` finds the workflow under `.g1t/` after the move. See [actions and workflows from other repositories](#actions-and-workflows-from-other-repositories). |
69| `actions/checkout` | The action itself, at the version the step names, fetching from g1t with the job's token: every input and output, its post step that removes the credential, and a pull request's [test merge commit](#what-a-pull-requests-run-builds), which g1t keeps in the repository. In a job container without git it downloads the commit as a tarball from [`GET /repos/{owner}/{repo}/tarball/{ref}`](/reference/api/repositories/download-tarball-archive/), as on GitHub, up to 10,000 files and 24 MB. `lfs` finds no Git LFS files: g1t keeps none. |
70| `GITHUB_OUTPUT`, `GITHUB_ENV`, `GITHUB_PATH`, `GITHUB_STATE`, `GITHUB_STEP_SUMMARY` | The same. Step summaries show on the run's page; see [job summaries](#job-summaries). |
71| `::error::`, `::warning::`, `::notice::`, `::group::`, `::add-mask::`, `::echo::on` | The same: errors and warnings become annotations on the run, with their `file`, `line`, `col`, `endLine` and `endColumn`, and [masked](#masking-secrets) values stay hidden. |
72| Problem matchers (`::add-matcher::`, `::remove-matcher::`) | The same, so `actions/setup-node`, `setup-go` and `setup-python` turn compiler and linter output into annotations. See [problem matchers](#problem-matchers). |
73| `secrets.*`, `vars.*`, `secrets.GITHUB_TOKEN` | The same. `secrets.G1T_TOKEN` is [the job's own token](#the-jobs-token); `GITHUB_TOKEN` is its alias. |
74| `environment:` on a job | The job waits for the environment's [protection rules](#environments), then reads each key's row for that environment, as environment secrets work, and the run records a [deployment](/guides/deployments-api/#deployments-from-g1t-actions) to it. `url` gives the deployment its address; `deployment: false` reads the environment's values without making one. The name may be an expression. |
75| `actions/upload-artifact`, `actions/download-artifact`, `actions/upload-artifact/merge` | The same inputs and outputs as version 4: `retention-days`, `overwrite`, `compression-level`, `include-hidden-files`, `!` exclusions, download by `pattern` with `merge-multiple`, and from another run with `run-id` and `github-token`. Up to 5 GiB each; see [artifacts](#artifacts). |
76| `actions/cache`, `actions/cache/restore`, `actions/cache/save` | The actions themselves, at the version the step names, saving to and restoring from the repository's cache: kept per repository and branch, found by `key` or the newest under a `restore-keys` prefix. Up to 2 GiB each; see [the cache](#the-cache). |
77| Actions that cache through the toolkit, such as `actions/setup-node` with `cache: npm` or `Swatinem/rust-cache` | The same: they save to and restore from the repository's cache. See [actions built on the toolkit](#actions-built-on-the-toolkit). |
78| sccache with `SCCACHE_GHA_ENABLED` | The same: its entries go to the repository's cache. See [caching Rust builds](#caching-rust-builds). |
79| `permissions: id-token: write` | The job can ask for an OIDC token, and trade it for a cloud provider's credentials. See [OIDC tokens](#oidc-tokens). |
80| `docker build`, `push`, `run`, `login`, `compose`, Buildx | The same, with a Docker Engine of the job's own. See [Docker](#docker). |
81| `services:` | The same: each service starts before the steps, health checks are waited for, and it is reached at `localhost` on its port and by its name. |
82| `container:` | The same: every step runs inside the image. |
83| `uses: docker://image`, Docker actions (`runs.using: docker`) | The same: built from the action's Dockerfile or pulled, and run with GitHub's `/github/workspace` layout. |
84| `docker/setup-buildx-action`, `docker/build-push-action`, `docker/login-action` | The same. `setup-buildx-action` picks the job's own Engine as the builder. |
85
86The **Actions** page of a workflow says, under *How this runs on g1t*,
87anything in it that runs differently.
88
89### Not yet
90
91- **Windows and macOS on g1t's machines.** g1t's own runners are Linux; a
92 job with `runs-on: windows-latest` or `macos-latest` fails, and says so.
93 [Self-hosted runners](/guides/self-hosted-runners/) of any OS run them:
94 `runs-on: [self-hosted, windows]`.
95- **Docker's `type=gha` build cache.** Buildx skips it on g1t, and the
96 build runs without a cache. Use a registry cache instead; see
97 [caching image builds](#caching-image-builds).
98- **Actions that upload artifacts with the toolkit's artifact library
99 themselves.** The library refuses to run against any server but
100 github.com. `actions/upload-artifact`, `actions/download-artifact` and
101 `actions/upload-artifact/merge` work, because g1t runs them itself. See
102 [actions built on the toolkit](#actions-built-on-the-toolkit).
103- **`on: label`, and some activity types.** Creating, editing or deleting
104 a label starts no workflows yet, and the workflow's page says so. Nor do
105 the `pull_request` types `unassigned`, `locked` and `unlocked`, `edited`
106 for a new title or description, or the `issues` types `unassigned`,
107 `locked`, `unlocked`, `pinned` and `unpinned`.
108- **Background steps.** A step with `background: true`, or a `wait`,
109 `wait-all`, `cancel` or `parallel` step, is refused: g1t does not run
110 them yet.
111- **`timezone` on a schedule, `concurrency.queue`, `cancel-timeout-minutes`
112 and `snapshot`.** The workflow runs without them, and its page says so:
113 a schedule runs in UTC, a group keeps one run waiting, a cancelled job
114 stops in its usual time, and no runner image is made.
115
116Why each of these is missing, and what to use instead, is on
117[What g1t can't do yet](/about/limitations/#actions-and-runners).
118
119## Skipping a run
120
121A commit can ask not to start the `push` and `pull_request` workflows it
122would, as on GitHub: put `[skip ci]`, `[ci skip]`, `[no ci]`,
123`[skip actions]` or `[actions skip]` anywhere in its message, or end the
124message with a `skip-checks: true` trailer. For a push, the message read
125is the pushed head's; for a pull request, its head commit's. Other
126events, such as `pull_request_target`, `workflow_dispatch` and
127`schedule`, still start their workflows.
128
129```sh
130git commit -m "Fix a typo in the README [skip ci]"
131```
132
133A required check that a skipped workflow would have reported stays
134pending, so a pull request whose merge needs it waits for a push that
135runs it.
136
137## How g1t checks a workflow
138
139g1t reads a workflow as GitHub does, from GitHub's own schema for workflow
140files, so a workflow GitHub refuses is refused on g1t too, and one GitHub
141runs reads on g1t. When a workflow can't be read, its page says what is
142wrong, with the job and step, and no run starts from it.
143
144| You write | g1t says |
145| --- | --- |
146| A key GitHub doesn't have, at any level, such as `runs_on:` or `with:` on a `run:` step | It is unexpected, and lists the keys that place can have. |
147| A job without `runs-on` | The job has no `runs-on`. A job that calls a reusable workflow with `uses:` has no `runs-on` or `steps`. |
148| A job id that doesn't start with a letter or `_`, has characters other than letters, digits, `-` and `_`, or starts with `__` | It is not a job id GitHub accepts. Step ids follow the same rule. Two jobs, or two steps of a job, whose ids differ only in case are the same id. |
149| An event GitHub doesn't have, or a filter the event doesn't take, such as `types` on `push` | GitHub has no such event, or the event doesn't take that key. `branches` and `branches-ignore` (and `tags`, `paths`) can't both be on one event. |
150| A YAML merge key (`<<: *defaults`) | GitHub doesn't read merge keys. Anchors and aliases (`&steps` and `*steps`) work, as on GitHub, for a whole value. |
151| A `permissions` name GitHub's token doesn't have, such as `metadata` | It is unexpected. A name GitHub has that g1t has nothing behind yet reads, and grants nothing. |
152
153### g1t's own settings
154
155A `g1t:` mapping can go at the top of a workflow, on a job and on a step.
156GitHub refuses a workflow with `g1t:` in it, so use it only in
157`.g1t/workflows`. It follows two rules:
158
159- It only adds to a workflow. It never changes what a GitHub key means.
160- Each setting in it has a GitHub equivalent, written here, so that a
161 workflow can be moved back to GitHub without it.
162
163| Setting | Where | What it does | On GitHub |
164| --- | --- | --- | --- |
165| `deployment: false` | A job | The job reads its `environment:` values without the run recording a deployment. | `environment: { name: ..., deployment: false }` |
166
167g1t keeps any other key under `g1t:` as written, and does nothing with it
168yet.
169
170## Managed workflows
171
172Some workflows run in a repository without a file in it: g1t installs
173them for a feature you turn on, such as a project's
174[build with a workflow](/guides/deployments/#build-with-a-workflow). A
175managed workflow runs like any other: on the **Actions** page, with its
176runs, logs, re-runs and checks, on g1t's machines or your
177[self-hosted runners](/guides/self-hosted-runners/). Its page says it is
178managed by g1t.
179
180A workflow of the same `name:` in `.g1t/workflows/` runs in its place, so
181to change one, commit your own version of it.
182
183## Workflows a workspace manages
184
185A workspace's owners can run a workflow in the workspace's repositories
186without committing it to each one, as GitHub's required workflows do. It
187runs in each repository it targets as that repository's own workflow: on
188the repository's events, with the repository's job token, secrets and
189variables, and reporting the repository's checks. Its path is
190`managed/<name>.yml`, and the repository's **Actions** page lists it as
191managed by the workspace, with the file it came from.
192
193To add one:
194
1951. Commit the workflow to one of the workspace's repositories, such as
196 `acme/policies/.g1t/workflows/scan.yml`.
1972. Open **Workspace → Code → Actions**, `g1t.sh/<workspace>/-/actions`, and
198 under **Workflows this workspace manages** choose **Add workflow**.
1993. Give it a name, its source as `owner/repo/path@ref` (for example
200 `acme/policies/.g1t/workflows/scan.yml@main`), a mode, and the
201 repositories it runs in, then choose **Add workflow**.
202
203| Setting | Options |
204| --- | --- |
205| Mode | **Required**: it runs in every repository it targets. A workflow of the same `name:` in the repository does not run while it is required, and nobody in the repository can turn it off. **Default**: it runs where the repository has no workflow of the same `name:`, which runs in its place, and the repository's admins can turn it off on its Actions page. |
206| Repositories | **All repositories**, or names and patterns, one per line: `*` matches any characters, and `!name` leaves out what an earlier line took in. The last line that matches decides, without regard to case; only `!` lines means every repository but those. |
207| Visibility | Public and private repositories, public only, or private only. |
208
209g1t copies the source file when you save, at the commit its ref names
210then, and that copy runs until you copy it again with **Copy again** (or
211save it with another source). So someone who can push to the source
212repository cannot change what runs with every repository's secrets: only
213an owner can, by copying it. The workflows list shows the commit each was
214copied at.
215
216- The source must be a workflow file in one of the workspace's own
217 repositories.
218- A source in a private repository runs in private repositories only, as a
219 public repository's workflows cannot use a private repository's: choose
220 **Private repositories only**, or g1t refuses it. A repository made
221 public later stops running it.
222- `uses: ./…` in the copy names the repository it runs in, as it would in a
223 file committed there. To call a reusable workflow of the source
224 repository, name it in full:
225 `uses: acme/policies/.g1t/workflows/build.yml@main`, which follows the
226 [rules for other repositories](#actions-and-workflows-from-other-repositories).
227- A required workflow runs for pull requests from forks and in the
228 [merge queue](/guides/merge-queue/) too, whatever the fork's files say.
229- When it changes, up to 20 repositories' workflow lists are brought up to
230 date at once. In a larger workspace the rest follow within minutes, or
231 as soon as someone opens their Actions page; their runs read the
232 workflows as they are meanwhile.
233
234To make a pull request wait for a required workflow, require its check in
235a [ruleset](/guides/rules/): its `name:` as the context, with
236`integration: actions`. A failure of the workflow fails the check whatever
237else reports a check of that name.
238
239Through the API and MCP:
240
241| Route | MCP | |
242| --- | --- | --- |
243| [`GET /workspaces/{workspace}/actions/managed-workflows`](/reference/api/run-protection/list-workspace-managed-workflows/) | `workflow` `list_managed_workflows` | Each with its source, the commit it was copied at, its mode, repositories, visibility and the copy that runs. |
244| [`POST /workspaces/{workspace}/actions/managed-workflows`](/reference/api/run-protection/create-workspace-managed-workflow/) | `workflow` `add_managed_workflow` | `name`, `source`, `mode` (`required` or `default`), `repositories`, `visibility` (`all`, `public` or `private`). |
245| [`PATCH /workspaces/{workspace}/actions/managed-workflows/{workflow}`](/reference/api/run-protection/update-workspace-managed-workflow/) | `workflow` `update_managed_workflow` | Any of those; `refresh: true` copies the source again. |
246| [`DELETE /workspaces/{workspace}/actions/managed-workflows/{workflow}`](/reference/api/run-protection/delete-workspace-managed-workflow/) | `workflow` `remove_managed_workflow` | Its runs stay. |
247
248Only owners can see and change them; a token needs `workspace:read` to
249list them and `workspace:admin` to change them.
250
251## Schedules
252
253A workflow with `on: schedule` runs on the default branch's latest commit,
254at each time its cron lines name, in UTC:
255
256```yaml
257on:
258 schedule:
259 - cron: "0 9 * * mon" # Mondays at 09:00 UTC
260 - cron: "*/30 * * * *" # every 30 minutes
261```
262
263Each line has five fields: minute, hour, day of month, month and day of
264the week. A field takes `*`, a number, a range `1-5`, a list `1,15` and a
265step `*/10`; days take `mon` to `sun`, and months `jan` to `dec`.
266
267| Rule | What happens |
268| --- | --- |
269| Every 5 minutes at most | A schedule more frequent than every 5 minutes, such as `* * * * *` or `*/2 * * * *`, runs every 5 minutes instead, on the five-minute marks (:00, :05, :10 and so on), at each mark that ends five minutes in which it would have run. A mark outside the hours, days or months the schedule names never runs: `* 9 * * *` runs from 09:00 to 09:55. |
270| No push for 60 days | Schedules pause in a repository that has had no push for 60 days. The next push to any branch resumes them. Other events and `workflow_dispatch` still start the workflow. |
271| Actions not paid for | When a scheduled run's job could not start because the workspace's plan, spend limit or the open-source pool does not cover it, that run fails and says why, and the workflow's schedule waits an hour before it tries again. |
272| Archived repository | Schedules wait until the repository is unarchived. |
273
274## Actions and workflows from other repositories
275
276A step's `uses: owner/repo@ref` (or `owner/repo/path@ref`) and a job's
277`uses: owner/repo/.g1t/workflows/build.yml@ref` name another repository.
278g1t looks for it on g1t first, but a name means what it means on GitHub
279whenever GitHub has it:
280
281| The repository | What happens |
282| --- | --- |
283| On g1t and public: in your workspace, under `g1t/` (g1t's own), or with a name GitHub has no public repository of | Your workflows use it, from any workspace. |
284| On g1t, private, in your workspace, with **Access** set to *Accessible from repositories in* the workspace | Your private repositories' workflows use it. A job reads it with a read-only token for that repository alone, which ends with the job. |
285| On g1t, private, and not shared that way | The step or job fails, and says why. A private repository's actions are never used by a public repository's workflows, whose logs anyone can read, nor from another workspace. |
286| Under `g1t/`, and not one of g1t's own public repositories | The step or job fails, and says so. A `g1t/` name is never looked for on GitHub. |
287| Not on g1t, or private in a workspace you cannot see | An action comes from GitHub, at the commit its ref names there (see [actions from GitHub](#actions-from-github)); a reusable workflow is read from a public repository on GitHub. |
288| On g1t, public, in another workspace, with a name GitHub has a public repository of | GitHub's is used, as on GitHub: anyone can make a workspace, so a workspace called `actions` never answers for `actions/setup-node`. When g1t can't reach GitHub to tell, GitHub's is used too. |
289
290An owner that is a [workspace alias](/guides/workspaces/#workspace-aliases)
291is looked for under its workspace's slug, by the same rules, as if you had
292written that slug: the alias decides where g1t looks, never who may use
293what it finds.
294
295`ref` is a branch, a tag or a commit. A reusable workflow may be under
296`.g1t/workflows/` or `.github/workflows/`; a `.github/workflows/` path
297also finds the file under `.g1t/workflows/` in a repository moved to
298g1t. A `./.g1t/workflows/…` call inside a workflow from another
299repository reads from that repository, at the same ref.
300
301To share a private repository's actions and workflows with the rest of its
302workspace, an admin chooses **Settings → Actions → Access → Accessible from
303repositories in** the workspace, or calls
304`PUT /repos/{owner}/{repo}/actions/permissions/access` with
305`{"access_level": "organization"}` (`none` to stop).
306
307### Actions from GitHub
308
309g1t keeps the actions your workflows use from GitHub, so a job downloads
310them from g1t rather than from GitHub:
311
3121. Before the first step, the job downloads every action its steps name,
313 as GitHub's runner does in **Set up job**, and fails there if one can't
314 be had. Its log names each one's commit:
315 `Download action repository 'actions/setup-node@v4' (SHA:…) from g1t`.
3162. g1t reads which commit the ref (`v4`, a branch, or a commit) names on
317 GitHub, a tag before a branch of the same name, and looks again at
318 most every 5 minutes, so a tag that moves is followed within minutes.
3193. The first job to use an action at a commit has g1t fetch that commit's
320 archive from GitHub and keep it, which can take up to two minutes.
321 While it waits, **Set up job** says so every 5 seconds
322 (`Waiting for g1t to fetch 'actions/setup-node@v4' (10s)`). Every
323 later job, in any workspace, downloads the same bytes from g1t.
324
325While GitHub doesn't answer, a ref reads as the commit it last named, and
326the job says so in a warning; an action g1t keeps still downloads. An
327action whose archive is larger than 32 MB, or from a private repository
328on GitHub, is downloaded from GitHub by the job itself. An archive no job
329has used for 30 days is deleted, and fetched again when next used.
330Kept archives are g1t's and count toward no workspace's storage.
331
332Actions that a composite action uses are downloaded when it runs.
333
334### g1t's own actions
335
336g1t's own actions live under `g1t/` on g1t.sh, public, with a `v1` tag
337that moves with each compatible release. `g1t` is a
338[workspace alias](/guides/workspaces/#workspace-aliases) of `flagon-io`,
339the workspace of Flagon, Inc., which builds g1t: `g1t/deploy@v1` is the
340repository `flagon-io/deploy`, and its page and the job's log name it
341that way. Write `g1t/`; it keeps working wherever g1t's actions are kept.
342
343| Action | What it does |
344| --- | --- |
345| [`g1t/deploy@v1`](https://g1t.sh/g1t/deploy) | Builds the checkout and puts it on [g1t.page](/guides/deployments/): production from the project's production branch, a preview from a pull request or another branch. The job needs `deployments: write`. See [deploying from a workflow](/guides/deployments/#deploying-from-a-workflow). |
346| [`g1t/detect@v1`](https://g1t.sh/g1t/detect) | Says what that build would find (`kind`, `install`, `build`, `output-dir`, `framework`) without building anything. |
347
348g1t's reusable workflows are in `g1t/workflows` the same way, such as
349`g1t/workflows/.g1t/workflows/deploy.yml@v1`, which a
350[project's](/guides/projects/) deployments run.
351
352A `g1t/` name always means g1t's own: it answers even where GitHub has a
353repository of the same name, only its public repositories answer for it,
354and a `g1t/` name that is not one of them fails rather than being looked
355for on GitHub.
356
357Both actions are composite actions around `g1t-runner`, which g1t's machines
358have; on a [self-hosted runner](/guides/self-hosted-runners/) they use
359the one on `PATH` or download the newest release.
360
361### Secrets for a called workflow
362
363A called workflow gets only the secrets its caller passes, plus
364`G1T_TOKEN` (`GITHUB_TOKEN`):
365
366```yaml
367jobs:
368 build:
369 uses: acme/shared/.g1t/workflows/build.yml@v2
370 with:
371 node-version: 24
372 secrets:
373 npm-token: ${{ secrets.NPM_TOKEN }}
374
375 deploy:
376 uses: ./.g1t/workflows/deploy.yml
377 secrets: inherit
378```
379
380- `secrets:` with names passes each as the called workflow names it,
381 read from the caller's `secrets`, `needs`, `inputs`, `matrix`,
382 `github` and `vars`.
383- `secrets: inherit` passes every secret the caller has.
384- A job in the called workflow with its own `environment:` also reads that
385 environment's secrets, over what was passed.
386- A secret the called workflow marks `required: true` under
387 `on.workflow_call.secrets` that the caller does not pass fails the
388 calling job before anything runs.
389
390`vars` are the calling repository's, and a called workflow's jobs run
391with the calling run's `github` context: `actions/checkout` checks out the
392calling repository.
393
394A secret passed by name that the called workflow does not declare under
395`on.workflow_call.secrets` fails the calling job: `Invalid secret: … is
396not defined in …`.
397
398### Inputs for a called workflow
399
400The values under `with:` are checked against the `type` each input has
401under `on.workflow_call.inputs`, before anything runs:
402
403- A `boolean` input takes `true` or `false`, and a `number` input a
404 number. Text such as `'true'` or a job's output (outputs are always
405 text) fails the calling job: pass `${{ fromJSON(needs.build.outputs.count) }}`
406 to give its value.
407- A `string` input takes text; a number or a boolean becomes its text.
408- An input you leave out takes its `default`, else `false`, `0` or `''`
409 by its type. A `required: true` input you leave out fails the calling
410 job.
411- A name under `with:` that the called workflow does not declare fails the
412 calling job: `Invalid input: … is not defined in …`.
413
414### A matrix of calls
415
416A job that calls a reusable workflow may have a `strategy`. Each
417combination of its matrix calls the workflow once, with `matrix` read in
418`with:` and `secrets:`:
419
420```yaml
421jobs:
422 test:
423 strategy:
424 matrix:
425 node: [20, 22]
426 max-parallel: 1
427 uses: ./.g1t/workflows/test.yml
428 with:
429 node-version: ${{ matrix.node }}
430```
431
432The called workflow's jobs are named under the combination's call:
433`test (20) / unit`, `test (22) / unit`. `max-parallel` limits how many
434calls run at a time, and with `fail-fast` (the default) a call that fails
435cancels the calls still running or waiting. `concurrency` on the calling
436job holds its group while the called workflow runs, as a job's own does.
437
438A run connects at most ten levels of workflows: the workflow it started
439from and nine levels of reusable workflows below it. It calls at most 50
440different reusable workflows; a workflow called again, as by each
441combination of a matrix, counts once. A call past either limit fails the
442calling job and says why.
443
444## Releases
445
446Workflows with `on: release` start when a release changes, at the commit
447its tag names (`GITHUB_REF` is `refs/tags/<tag>`). Each change is one or
448more activity types, which `types:` chooses among:
449
450| Change | Activity types |
451| --- | --- |
452| A draft made | `created` |
453| A release made and published | `created`, `published`, and `released` (or `prereleased` for a prerelease) |
454| A draft published | `published`, and `released` or `prereleased` |
455| A prerelease made a full release | `edited` and `released` |
456| Made a draft again | `unpublished` |
457| Title, notes or prerelease changed | `edited`, with `github.event.changes` holding the old title and notes |
458| Deleted (the tag stays) | `deleted` |
459
460```yaml
461on:
462 release:
463 types: [published]
464```
465
466`github.event.release` has `tag_name`, `name`, `body`, `draft`,
467`prerelease`, `target_commitish`, `author` and `html_url`. A release a
468job's own token makes or changes starts no workflows.
469
470## Deployments
471
472`on: deployment` starts when a deployment is made, and
473`on: deployment_status` when one has a new status: one reported through
474the [deployments API](/guides/deployments-api/) or a
475[g1t.page](/guides/deployments/) build. The run is at the commit deployed;
476`GITHUB_REF` is the branch or tag deployed, and empty for a bare commit.
477
478```yaml
479on: deployment_status
480
481jobs:
482 smoke:
483 if: github.event.deployment_status.state == 'success'
484 runs-on: ubuntu-latest
485 steps:
486 - run: curl -fsS "${{ github.event.deployment_status.environment_url }}"
487```
488
489`github.event.deployment` has `environment`, `ref`, `sha`, `task` and
490`payload`; `github.event.deployment_status` has `state`, `environment_url`
491and `log_url`. Deployments a workflow makes, with `environment:` or with
492its job's token, start no workflows, so a workflow cannot set itself off.
493
494## The runner
495
496Jobs run in a fresh sandbox each: Debian with Node 24, Python 3, Go, Rust,
497Java 21, .NET 8, Ruby 3.3, `build-essential`, `git`, `curl`, `jq`, Docker
498(with Buildx and Compose) and passwordless `sudo`, in GitHub's layout
499(`/home/runner/work`, `RUNNER_WORKSPACE`, `RUNNER_TEMP`, `RUNNER_TOOL_CACHE`).
500`runner.os` is `Linux`. Each step's `GITHUB_ACTION` is its `id`, or
501GitHub's name for it: `__run`, `__run_2` and on for `run:` steps,
502`__actions_checkout` for `uses: actions/checkout`. `ImageOS` and
503`ImageVersion` are set when the machine's image sets them. `ubuntu-latest`, `ubuntu-24.04` and other Linux
504labels all run here. A job whose `runs-on` names `self-hosted` waits for one
505of your [self-hosted runners](/guides/self-hosted-runners/) instead. Setup actions such as
506`actions/setup-node` and `actions/setup-python` install other versions as
507they do on GitHub.
508
509### Languages and their setup actions
510
511Each language below is on `PATH` from the job's first step, so a workflow
512that only runs `java`, `dotnet` or `ruby` needs no setup step. When it has
513one, the setup action finds the version that is already there and
514downloads nothing.
515
516| Language | Version | Where | Setup action |
517| --- | --- | --- | --- |
518| Java | Eclipse Temurin 21 (LTS), JDK | `JAVA_HOME` (also `JAVA_HOME_21_X64`), in `RUNNER_TOOL_CACHE` | `actions/setup-java` with `distribution: temurin` and `java-version: 21` uses it. Other versions and distributions are downloaded. |
519| .NET | SDK 8 (LTS) | `DOTNET_ROOT`, `/usr/share/dotnet` | `actions/setup-dotnet` with `dotnet-version: 8.0.x` keeps it when it is the newest 8.0 SDK, and installs other SDKs beside it. |
520| Ruby | 3.3, with Bundler | in `RUNNER_TOOL_CACHE` | `ruby/setup-ruby` with `ruby-version: '3.3'` (or a `.ruby-version` naming 3.3) uses it. |
521| Node | 24 | `/usr/local/bin` | `actions/setup-node` installs other versions. |
522| Python | 3.11 | `/usr/bin/python3` | `actions/setup-python` installs other versions. |
523| Go | 1.27 | `/usr/local/go` | `actions/setup-go` installs other versions. |
524| Rust | stable, with `rustfmt`, `clippy` and the `wasm32-unknown-unknown` target | `~/.cargo/bin` | `rustup` is there to add toolchains and targets. |
525
526```yaml
527steps:
528 - uses: actions/checkout@v5
529 - uses: actions/setup-java@v5
530 with:
531 distribution: temurin
532 java-version: 21
533 - run: ./gradlew build
534```
535
536Because the sandbox runs Debian, `ruby/setup-ruby` treats it as a
537self-hosted runner and uses only the Rubies in `RUNNER_TOOL_CACHE`. A version other than 3.3 fails at that step; install
538it in a `run` step instead (for example with `ruby-build`) or run the job
539in a `container:` with the Ruby you need, such as `ruby:3.4`.
540
541The headers that gems and .NET need to build native code (`libyaml`,
542`libffi`, `zlib`, OpenSSL, ICU) are installed too.
543
544### Calling g1t's API from a job
545
546The `gh` command is not installed: it needs a GraphQL API, and g1t's API
547is REST. Call it with `curl`, using the job's token and the API's address,
548which every job has as `GITHUB_API_URL`:
549
550```yaml
551- name: Comment on the pull request
552 env:
553 TOKEN: ${{ github.token }}
554 run: |
555 curl -fsS -X POST "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/issues/${{ github.event.number }}/comments" \
556 -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
557 -d '{"body": "Built."}'
558```
559
560The token reaches this repository and does what the job's `permissions:`
561say; see [the job's token](#the-jobs-token). The
562[API reference](/reference/api/) lists every endpoint.
563
564### Machine sizes
565
566A job runs on the standard machine unless its `runs-on` names a larger
567one:
568
569| `runs-on` | vCPUs | Memory | Disk |
570| --- | --- | --- | --- |
571| `ubuntu-latest`, or any other Linux label | 0.5 | 4 GiB | 8 GB |
572| `g1t-2core` | 2 | 8 GiB | 16 GB |
573| `g1t-4core` | 4 | 12 GiB | 20 GB |
574
575```yaml
576jobs:
577 build:
578 runs-on: g1t-4core
579```
580
581The label can come from the matrix or the run's inputs
582(`runs-on: ${{ matrix.big && 'g1t-4core' || 'ubuntu-latest' }}`). A
583larger machine costs what it costs g1t, plus the same margin as all
584sandbox time: see [usage and billing](/guides/usage-and-billing/#workflow-jobs-on-larger-machines).
585Builds that compile, such as Rust or a large TypeScript project, finish
586several times faster on one.
587
588A job on g1t's machines runs for at most 60 minutes, whatever its
589`timeout-minutes`, and the workflow's page warns about a job that asks
590for more. One on a self-hosted runner runs for 360 minutes when it names
591no `timeout-minutes`, as on GitHub, and can ask for up to 24 hours. A job
592stopped at its time cap fails saying so.
593
594g1t's machines are x64. A job whose `runs-on` names an Arm label, such
595as `ubuntu-24.04-arm`, runs on x64, and the workflow's page says so; a
596self-hosted Arm runner runs it on Arm.
597
598### What a job can reach
599
600A job's network is restricted, as an agent's is (see
601[guardrails](/guides/guardrails/)): it reaches the hosts its project's
602guardrails allow, g1t itself, and what builds need, and nothing else.
603What builds need is the package registries (npm, PyPI, crates.io, the Go
604proxy, RubyGems, Packagist, NuGet, Maven and Gradle, Debian's mirrors),
605GitHub, where `uses:` actions and the setup actions' downloads come from,
606the toolchains' download sites (`nodejs.org`, `go.dev`,
607`static.rust-lang.org`), and the public container registries (Docker Hub,
608GitHub's, Quay, and `mirror.gcr.io`, the mirror of Docker Hub that a job's
609Engine asks first). A request anywhere else gets `403` with
610the reason. To reach another host, someone with the Maintain [role](/guides/access-and-roles/) or
611higher adds it to the project's
612allowed domains under **Settings → Guardrails**; a project whose guardrails
613turn the network restriction off runs its jobs with an open network.
614
615A host only workflows should reach, such as the API a deploy uploads to,
616goes in **Workflow-only domains** instead, limited to the workflows and
617environments that need it: `api.cloudflare.com | deploy.yml | production`
618lets only `deploy.yml`'s jobs with `environment: production` reach it.
619Agents never reach those hosts, and neither do runs of pull requests from
620forks. See [workflow-only domains](/guides/guardrails/#workflow-only-domains).
621
622### Brokered secrets
623
624A secret a job uses only to call an API can be brokered: the job gets a
625stand-in (`g1tsec_…`) in place of `${{ secrets.KEY }}`, and g1t puts the
626real value on the job's requests to the hosts the secret names as they
627leave its sandbox, in headers (Basic auth included) and the query string.
628The value never enters the job, so neither its steps nor a compromised
629dependency can read it. A stand-in sent to any other host goes as it is,
630and the log says so once. This works on g1t's machines, whether the
631project's network is restricted or open; a job on a self-hosted runner
632gets the stand-in too, never the value, and its log warns that it will
633not work there yet. A secret the job must read itself (a database
634password over raw TCP, a signing key, a key to files it decrypts) cannot
635be brokered. See
636[brokered secrets](/guides/secrets-and-variables/#brokered-secrets).
637
638g1t does not run cryptocurrency miners: a step that names one (`xmrig`,
639a `stratum+tcp://` pool, `--donate-level`) is not run, and a job that
640looks like it is mining is stopped. See
641[abuse and mining](/guides/guardrails/#abuse-and-mining).
642
643## Docker
644
645Each job on g1t's machines has a Docker Engine of its own, inside the
646job's sandbox. Nothing runs until the job uses it: the first `docker`
647command, or a job's `services:` or `container:`, starts it, in a second
648or two, and the log says so. It ends with the job, with every image,
649container and build cache in it. No other job, repository or workspace
650ever shares it.
651
652```yaml
653jobs:
654 test:
655 runs-on: ubuntu-latest
656 services:
657 postgres:
658 image: postgres:17
659 env:
660 POSTGRES_PASSWORD: ${{ secrets.DB_PASSWORD }}
661 ports: ["5432:5432"]
662 options: >-
663 --health-cmd pg_isready --health-interval 5s --health-retries 10
664 steps:
665 - uses: actions/checkout@v5
666 - run: docker compose up -d --wait
667 - run: npm test
668 env:
669 DATABASE_URL: postgres://postgres:${{ secrets.DB_PASSWORD }}@localhost:5432/postgres
670```
671
672### What works
673
674| | On g1t's machines |
675| --- | --- |
676| `docker build`, `buildx build`, `run`, `exec`, `pull`, `push`, `login`, `compose` | Work as they do on GitHub's runners. The Engine, Buildx and Compose are current releases. |
677| `services:` | Pulled and started before the first step, with `env`, `ports`, `volumes`, `options` and `credentials`. Services with a health check are waited for; one that turns unhealthy fails the job with its log. Each service's log is printed when the job ends. `job.services.<id>.id`, `.network` and `.ports` are set. |
678| `container:` | Every `run` step and JavaScript action runs inside the image, with its `env`, `options`, `volumes` and `credentials`. The workspace, `RUNNER_TEMP` and the tool cache are mounted at the same paths as on g1t's runner. |
679| `uses: docker://image` | Pulled and run, with `with.args` and `with.entrypoint`. |
680| Docker actions | Built from the action's Dockerfile (or pulled, for `image: docker://…`), and run with its `args`, `env` and `entrypoint`, its inputs as `INPUT_*` variables, and `pre-entrypoint` and `post-entrypoint`. |
681| `docker/setup-buildx-action` | Selects the job's own Engine as the builder (BuildKit). Its `name`, `driver`, `platforms` and `nodes` outputs are set. `driver`, `driver-opts` and `buildkitd-*` are not used, and the log says so. |
682| `docker/build-push-action` | Works, with `push`, `load`, `tags`, `labels`, `build-args`, `secrets`, `target`, `provenance` and `sbom`. |
683| `docker/login-action` | Works, for g1t's registry, Docker Hub, GitHub's registry, Cloudflare's (`registry.cloudflare.com`) and any registry the job can reach. |
684
685### Services and the network
686
687Every container a job starts shares the job's own network, the one its
688[guardrails](/guides/guardrails/) apply to. So:
689
690- **A service is at `localhost`** on its port, from steps and from other
691 containers. `ports: ["5432:5432"]` and `ports: ["5432"]` both mean
692 `localhost:5432`.
693- **A port mapped to another number** (`ports: ["6543:5432"]`, or
694 `docker run -p 8080:80`) is forwarded: `localhost:6543` reaches the
695 service's 5432. `job.services.<id>.ports` says which port to use, and
696 `docker inspect` and `docker port` report it.
697- **A service is also reached by its name**, as it is from a job
698 container on GitHub: `postgres:5432` works from steps, from the job's
699 container and from any container started later. So do the names of
700 containers and Compose services, and their network aliases.
701- **Two containers cannot listen on the same port.** A job with a
702 `redis` service and a Compose file that starts another Redis on 6379
703 gets an error from the second; give one of them another port.
704
705A container that asks for `--network none` gets none, and
706`--network container:<name>` shares that container's.
707
708### Job containers
709
710With `container:`, the steps run inside the image as its default user,
711usually `root`. A few things differ from GitHub's runner:
712
713- The workspace is at the same path as on g1t's runner
714 (`/home/runner/work/…`), not `/__w`. `github.workspace` is correct
715 either way.
716- JavaScript actions run inside the container with g1t's Node 24, which
717 needs an image with glibc and `libstdc++` (Debian, Ubuntu and most
718 language images have both). In an image without them, such as Alpine,
719 they run beside the container, on g1t's runner, with the same files,
720 and the log says so.
721- The artifact actions run on g1t's runner, with the same files.
722
723### Building and pushing images
724
725On g1t's machines, a job is signed in to g1t's container registry from
726the start, with its own `G1T_TOKEN`, so it can push to and pull from its
727repository's images without a login step; images of other repositories
728need this one added under their
729[Manage Actions access](/guides/packages/#manage-actions-access). A run that gets no secrets is
730not signed in. See [container registry](/guides/containers/#in-workflows).
731
732```yaml
733jobs:
734 image:
735 runs-on: g1t-4core
736 steps:
737 - uses: actions/checkout@v5
738 - uses: docker/setup-buildx-action@v3
739 - uses: docker/build-push-action@v6
740 with:
741 push: true
742 tags: g1t.sh/${{ github.repository }}:${{ github.sha }}
743 cache-from: type=registry,ref=g1t.sh/${{ github.repository }}:buildcache
744 cache-to: type=registry,ref=g1t.sh/${{ github.repository }}:buildcache,mode=max
745```
746
747For other registries, sign in with `docker/login-action` or
748`docker login`, as on GitHub. Docker Hub's images are pulled through its
749public mirror first, so jobs are rarely held up by Docker Hub's limits on
750anonymous pulls.
751
752#### Caching image builds
753
754The Engine starts empty in every job, so a build's layers are rebuilt
755unless the job brings a cache:
756
757- **A registry cache** (`cache-to: type=registry,ref=…,mode=max`), in g1t's
758 registry or any other, is the simplest and is shared by every branch.
759- **A local cache** (`cache-to: type=local,dest=/tmp/buildx-cache`) saved
760 and restored with `actions/cache`, within [the cache's limits](#the-cache).
761- **`type=gha`** is not used on g1t yet: Buildx skips it, and the build
762 runs without a cache.
763
764### Limits
765
766- **Machine.** Containers share the job's machine: its vCPUs, memory and
767 disk ([machine sizes](#machine-sizes)). Image builds and databases want
768 `g1t-2core` or `g1t-4core`. `--cpus` and `--memory` limit a container
769 within that.
770- **Disk.** Images take room on the job's disk. On a machine whose disk
771 cannot hold layered images, the Engine stores plain copies, which take
772 more room; the log says when it does.
773- **Linux, amd64.** Images for other platforms need QEMU's emulators,
774 which g1t's machines do not have set up; `docker/setup-qemu-action` is
775 not supported there yet.
776- **Privileged containers** (`--privileged`) run, with no more reach than
777 the job itself has: the job's sandbox is the boundary.
778
779### How Docker is kept safe
780
781- **One Engine per job.** It runs inside the job's own sandbox, a virtual
782 machine of its own, and is gone with it. No Docker socket of g1t's, or of
783 any machine, is shared with a job.
784- **The job's guardrails hold.** Containers use the job's network, so a
785 container, a build step or an image pull reaches only what the job may
786 reach. A host off the list gets `403` with the reason, as any step does.
787- **HTTPS keeps working.** In a job whose network is restricted, every
788 container and build step is given the certificate the job's HTTPS is
789 checked with, in `/dev/g1t-egress`, and `SSL_CERT_FILE`,
790 `NODE_EXTRA_CA_CERTS`, `REQUESTS_CA_BUNDLE`, `CURL_CA_BUNDLE`, `PIP_CERT`,
791 `GIT_SSL_CAINFO` and `CARGO_HTTP_CAINFO` pointing at it, unless the
792 container sets them itself. None of it is written into an image's layers.
793 Tools that keep their own list of certificates, such as Java's, need it
794 added in the build that uses them.
795- **Short-lived credentials.** The registry sign-in uses the run's own
796 token, which ends with the run; `credentials:` for a service or a job
797 container are used for that pull only.
798- **No miners.** A container whose image or command names a miner is not
799 created, as a step's script is not run.
800
801## The cache
802
803`actions/cache` keeps what a job saves for the repository's later jobs:
804
805| | |
806| --- | --- |
807| One entry | Up to 2 GiB, compressed. A larger one is not saved, and the job goes on. |
808| A repository's entries | Up to 10 GiB together. Saving past it removes the entries restored longest ago. |
809| How long | Until it has not been restored for 7 days, and at most 28 days after it was saved. |
810| Which branch | An entry belongs to the branch, tag or pull request whose run saved it. A run restores from its own, then from the branch its pull request merges into, then from the default branch. |
811| Keys | Written once on each branch: saving under a key that exists there does nothing. On each branch in turn, a restore finds its `key` exactly, else the newest entry whose key starts with one of its `restore-keys`. |
812| `path` | Files and folders; globs, `**` included; `~/` is the home folder; a line starting with `!` leaves matching paths out. The same key saved for other paths is another entry. |
813| Compression | zstd. |
814
815So a feature branch can read what `main` saved, but `main` never reads
816what a feature branch saved, and a pull request from outside the
817repository saves where nothing else ever reads it: nobody can plant an
818entry that the default branch's builds restore. Actions that cache through the
819toolkit, such as `actions/setup-node` with `cache: npm`, follow the same
820rules.
821
822```yaml
823- uses: actions/cache@v4
824 with:
825 path: |
826 ~/.cargo/registry/cache
827 target/release
828 !target/**/incremental
829 key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
830 restore-keys: cargo-${{ runner.os }}-
831```
832
833Each restore and save says on the job's log how large the entry was and
834how long it took. A workspace on the plan pays for what its caches and
835[artifacts](#artifacts) hold (`Actions cache storage` on its statement),
836at R2's price plus the margin; see
837[usage and billing](/guides/usage-and-billing/#actions-cache).
838
839## Artifacts
840
841`actions/upload-artifact` keeps files a job made with its run, for later
842jobs, other runs and people:
843
844| | |
845| --- | --- |
846| One artifact | Up to 5 GiB, zipped. |
847| A run's artifacts | Up to 10 GiB together. |
848| How long | The repository's setting: 14 days unless someone with the Maintain role changes it under **Settings → Repository → Artifacts**, from 1 to 90 days. `retention-days` asks for fewer days, never more. |
849| Names | One artifact per name in a run. Uploading a name again fails, unless the upload says `overwrite: true`, which replaces it. A name is up to 256 characters, none of `" : < > \| * ? \ /`. |
850| `path` | Files, folders and globs, `**` included; a line starting with `!` leaves matching paths out. Files and folders whose names start with `.` are left out unless `include-hidden-files: true`. |
851| Compression | `compression-level` 0 (stored) to 9; 6 unless you say. |
852| Outputs | `artifact-id` (a number), `artifact-url` (its run's page) and `artifact-digest` (the SHA-256 of its zip). |
853
854```yaml
855- uses: actions/upload-artifact@v4
856 with:
857 name: web-dist
858 path: |
859 dist/
860 !dist/**/*.map
861 retention-days: 5
862 compression-level: 9
863```
864
865`actions/download-artifact` downloads one by `name` into `path`, or every
866artifact of the run, each into a folder of its name; `pattern` picks them
867by name, and `merge-multiple: true` puts them all in one folder.
868`artifact-ids` picks them by number. With `github-token` and `run-id`, it
869downloads from another run of the same repository, such as the one a
870`workflow_run` workflow follows:
871
872```yaml
873- uses: actions/download-artifact@v4
874 with:
875 name: web-dist
876 github-token: ${{ secrets.GITHUB_TOKEN }}
877 run-id: ${{ github.event.workflow_run.id }}
878```
879
880`actions/upload-artifact/merge` downloads the run's artifacts that match
881`pattern`, uploads them as one artifact (`name`, `merged-artifacts` unless
882you say), and deletes them with `delete-merged: true`.
883
884A run's summary lists its artifacts with their size and when they expire.
885Anyone who can see the run downloads them there; someone with the Write
886role can delete one before it expires.
887
888## Actions built on the toolkit
889
890`actions/cache` and many other actions save to the cache with GitHub's
891toolkit, `@actions/cache`: `actions/setup-node`, `actions/setup-python`,
892`actions/setup-go` and `actions/setup-java` with `cache:`,
893`Swatinem/rust-cache`, and others. They work on g1t as they are: every job gets `ACTIONS_RUNTIME_TOKEN`, `ACTIONS_CACHE_URL` and
894`ACTIONS_RESULTS_URL`, and g1t answers the toolkit's requests from the
895repository's cache.
896
897- Their entries are the repository's, under [the cache's](#the-cache)
898 limits, and are deleted the same way.
899- An entry is found by its key and by the version the toolkit names for
900 it, from its paths and compression, as on GitHub: an entry `setup-node`
901 saved is restored by `actions/cache` with the same key and paths, and
902 the other way round.
903- An entry the toolkit sends whole, which it does below 128 MB, is not
904 saved when it is over 100 MB, the most g1t takes in one request. The
905 step warns and the job goes on.
906- The toolkit's artifact library refuses to run against any server but
907 github.com, so an action that uploads artifacts with it directly fails
908 with its own message. `actions/upload-artifact`,
909 `actions/download-artifact` and `actions/upload-artifact/merge` work:
910 g1t runs those itself.
911
912## An action's pre and post
913
914A JavaScript action's `pre` runs before the job's first step, as on
915GitHub: each one is a step of its own, *Pre* and the step's name, in the
916order of the steps, when its `pre-if` holds (`always()` when it has
917none). Its `post` runs after the last step, *Post* and the step's name,
918last first. What `pre` saves with `GITHUB_STATE` or `::save-state::`
919reaches the step's main as `STATE_<name>`, and what either saves reaches
920`post`.
921
922An action in your repository (`uses: ./…`) is not checked out when the
923job starts, so its `pre` is not run and the step warns, as on GitHub. An
924action inside a composite action runs its `pre` right before its main.
925
926A step that gives an input the action does not declare is warned of:
927*Unexpected input(s) 'foo', valid inputs are ['token', 'path']*. The
928step still runs.
929
930## Problem matchers
931
932`::add-matcher::path/to/matcher.json` adds the matchers in a file of
933GitHub's format, and they read every line of output from then on, for
934the rest of the job. A line one matches becomes an error, warning or
935notice annotation, with the file, line, column and message it found, in
936place of the line in the log.
937
938```json
939{
940 "problemMatcher": [
941 {
942 "owner": "my-linter",
943 "pattern": [
944 {
945 "regexp": "^(.+):(\\d+):(\\d+): (error|warning) (.*)$",
946 "file": 1, "line": 2, "column": 3, "severity": 4, "message": 5
947 }
948 ]
949 }
950 ]
951}
952```
953
954- A pattern of several lines matches them in order. The last one can
955 set `loop` to match each issue after a header line, as ESLint's
956 stylish output has.
957- `severity` is the matcher's own default, `error` when it gives none.
958 A severity other than `error`, `warning` or `notice` is no annotation.
959- A file is kept when it exists in a repository in the workspace, as a
960 path inside it; `fromPath` names the file a relative path is from.
961- A matcher added with an owner already there takes its place.
962 `::remove-matcher owner=my-linter::` removes it.
963- Patterns are regular expressions as Rust's `regex` reads them, which
964 has no lookaround or backreferences. A file whose patterns do not
965 read is refused, with an error in the log.
966
967`::echo::on` shows each workflow command in the log as it is read,
968until `::echo::off`; with [debug logging](#debug-logging) they are shown
969from the start.
970
971## Caching Rust builds
972
973A checkout gives every file a new modification time, so Cargo compiles
974your workspace's own crates again even when `target/` was restored from
975the cache. [sccache](https://github.com/mozilla/sccache) caches each
976compiler call by what it compiles (the source, the flags, the toolchain
977and the dependencies), so a crate that did not change comes back from the
978cache instead. Its GitHub Actions backend works on g1t as it is: it keeps
979its entries in the repository's cache, under [the cache's](#the-cache)
980limits and branch rules.
981
9821. Install sccache, pinned to a release and checked against its checksum.
9832. Set `RUSTC_WRAPPER: sccache` and `SCCACHE_GHA_ENABLED: "true"`. The
984 job's `ACTIONS_RUNTIME_TOKEN` and `ACTIONS_CACHE_URL` are already in
985 every step's environment; no step needs to export them.
9863. Set `CARGO_INCREMENTAL: "0"`: sccache does not cache incremental
987 builds, and a fresh checkout gains nothing from them.
988
989```yaml
990name: CI
991on:
992 pull_request:
993 push:
994 branches: [main]
995
996jobs:
997 test:
998 runs-on: ubuntu-latest
999 env:
1000 RUSTC_WRAPPER: sccache
1001 SCCACHE_GHA_ENABLED: "true"
1002 CARGO_INCREMENTAL: "0"
1003 steps:
1004 - uses: actions/checkout@v5
1005 - name: Install sccache
1006 env:
1007 VERSION: v0.18.0
1008 SHA256: 45f1447fbe231e3037bde351ef70677dd212216c8d62ae7ca409fecc4d6acc89
1009 run: |
1010 name="sccache-$VERSION-x86_64-unknown-linux-musl"
1011 curl -fsSL -o "$RUNNER_TEMP/sccache.tar.gz" \
1012 "https://github.com/mozilla/sccache/releases/download/$VERSION/$name.tar.gz"
1013 echo "$SHA256 $RUNNER_TEMP/sccache.tar.gz" | sha256sum -c -
1014 tar -xzf "$RUNNER_TEMP/sccache.tar.gz" -C "$RUNNER_TEMP"
1015 install -m 0755 "$RUNNER_TEMP/$name/sccache" "$HOME/.cargo/bin/sccache"
1016 - run: cargo test --workspace --locked
1017 - name: sccache's hits and misses
1018 if: always()
1019 run: |
1020 echo '```' >> "$GITHUB_STEP_SUMMARY"
1021 sccache --show-stats | tee -a "$GITHUB_STEP_SUMMARY"
1022 echo '```' >> "$GITHUB_STEP_SUMMARY"
1023```
1024
1025What to expect:
1026
1027| | |
1028| --- | --- |
1029| What is cached | Every library crate (`rlib`): your workspace's crates and their dependencies. |
1030| What is compiled each time | What rustc links: binaries, `cdylib` crates, proc macros, build scripts and test harnesses. |
1031| Its entries | One per compiled crate, often thousands for a workspace, each a few KB to a few MB. They count toward the repository's 10 GiB like any other entry, and the ones not restored for 7 days are deleted. |
1032| Branches | A pull request reads what the default branch saved. Run the workflow on pushes to the default branch too, as above, or each pull request's first run starts with an empty cache. |
1033| Starting over | Set `SCCACHE_GHA_VERSION` to any new value. A new sccache release starts over by itself. |
1034| If the cache cannot be reached | sccache refuses to start. Set `SCCACHE_IGNORE_SERVER_IO_ERROR: "1"` to have rustc run without it instead. |
1035
1036sccache adds to `actions/cache` rather than replacing it: keep caching
1037`~/.cargo/registry/cache` and `target/` keyed by `Cargo.lock`, so Cargo
1038does not call rustc at all for dependencies that did not change, and
1039sccache answers the calls it still makes for your own crates.
1040`Swatinem/rust-cache` works the same way.
1041
1042## OIDC tokens
1043
1044A job can prove which repository, branch and environment it runs for with
1045a short-lived OpenID Connect token signed by g1t, and trade it for a cloud
1046provider's credentials. Nothing long-lived needs to sit in a secret.
1047
10481. Give the job, or the workflow, `permissions: id-token: write`. A job
1049 without it gets no token, and neither does a run of a pull request from
1050 outside the repository.
10512. Tell your cloud to trust g1t's issuer for your repository (below).
10523. Use the provider's own login action, which asks for the token.
1053
1054```yaml
1055permissions:
1056 id-token: write
1057 contents: read
1058
1059jobs:
1060 deploy:
1061 runs-on: ubuntu-latest
1062 environment: production
1063 steps:
1064 - uses: aws-actions/configure-aws-credentials@v4
1065 with:
1066 role-to-assume: arn:aws:iam::123456789012:role/acme-web-deploy
1067 aws-region: us-east-1
1068```
1069
1070The job gets `ACTIONS_ID_TOKEN_REQUEST_URL` and
1071`ACTIONS_ID_TOKEN_REQUEST_TOKEN`, which `core.getIDToken()` reads. To ask
1072for a token yourself, name its audience:
1073
1074```sh
1075curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
1076 "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://deploy.example.com" | jq -r .value
1077```
1078
1079| | |
1080| --- | --- |
1081| Issuer | `https://api.g1t.sh/actions/oidc` |
1082| Discovery | `https://api.g1t.sh/actions/oidc/.well-known/openid-configuration` |
1083| Keys | `https://api.g1t.sh/actions/oidc/.well-known/jwks`, RS256, each with its `kid` |
1084| Lifetime | 5 minutes |
1085| Audience | What the job asks for; `https://g1t.sh/<owner>` when it asks for none |
1086
1087A job without `id-token: write` has no `ACTIONS_ID_TOKEN_REQUEST_URL`, so
1088a login action says it cannot find one; a token asked for anyway is
1089refused with `403` and `This job has no OIDC token`.
1090
1091### Reaching your cloud
1092
1093Asking for the token goes to `api.g1t.sh`, which every job reaches. Trading
1094it for credentials goes to your cloud's token service, which a job reaches
1095only when its project allows it (see [what a job can reach](#what-a-job-can-reach)).
1096Add the hosts under **Settings → Guardrails → Workflow-only domains**,
1097limited to the workflow and environment that deploy, so agents and other
1098workflows never reach them:
1099
1100```text
1101sts.us-east-1.amazonaws.com | deploy.yml | production
1102s3.us-east-1.amazonaws.com | deploy.yml | production
1103sts.googleapis.com | deploy.yml | production
1104iamcredentials.googleapis.com | deploy.yml | production
1105login.microsoftonline.com | deploy.yml | production
1106management.azure.com | deploy.yml | production
1107```
1108
1109| Cloud | Token exchange | Then |
1110| --- | --- | --- |
1111| AWS | `sts.<region>.amazonaws.com`, the region `aws-region` names | Each service the job uses, such as `s3.<region>.amazonaws.com`, or `*.amazonaws.com` for all of them |
1112| Google Cloud | `sts.googleapis.com`, `iamcredentials.googleapis.com` | Each API the job uses, such as `run.googleapis.com` |
1113| Azure | `login.microsoftonline.com` | `management.azure.com`, and each service the job uses |
1114
1115Without them, the login step fails with `403` and a line naming the host
1116that is not allowed.
1117
1118### Claims
1119
1120Each token carries the claims GitHub's do, so trust policies written for
1121those read g1t's the same way.
1122
1123| Claim | Example |
1124| --- | --- |
1125| `sub` | `repo:acme/web:environment:production` for a job with an `environment:`; `repo:acme/web:pull_request` for a pull request's run; otherwise `repo:acme/web:ref:refs/heads/main` (or `refs/tags/v1.2.0`) |
1126| `repository`, `repository_owner` | `acme/web`, `acme` |
1127| `repository_id`, `repository_owner_id` | g1t's ids for them, such as `rep_01kpw0…` (not the numbers `github.repository_id` and `github.repository_owner_id` give, so trust policies already written keep matching) |
1128| `repository_visibility` | `public` or `private` |
1129| `ref`, `ref_type`, `ref_protected`, `sha` | `refs/heads/main`, `branch`, `"true"`, the commit |
1130| `head_ref`, `base_ref` | A pull request's branches |
1131| `environment` | The job's environment, when it has one |
1132| `event_name` | `push`, `pull_request`, `workflow_dispatch`, … |
1133| `workflow`, `workflow_ref`, `workflow_sha` | `Deploy`, `acme/web/.g1t/workflows/deploy.yml@refs/heads/main`, the commit |
1134| `job_workflow_ref`, `job_workflow_sha` | The workflow that defines the job: a called workflow's own file |
1135| `run_id`, `run_number`, `run_attempt` | `run_01kq9c…`, `"12"`, `"1"` |
1136| `actor`, `actor_id` | Who started the run |
1137| `runner_environment` | `github-hosted` on g1t's machines, `self-hosted` on yours |
1138| `iss`, `aud`, `jti`, `iat`, `nbf`, `exp` | As in any OIDC token |
1139
1140### AWS
1141
11421. Add g1t as an identity provider, under **IAM → Identity providers**:
1143 provider type **OpenID Connect**, provider URL
1144 `https://api.g1t.sh/actions/oidc`, audience `sts.amazonaws.com`. Or:
1145
1146 ```sh
1147 aws iam create-open-id-connect-provider \
1148 --url https://api.g1t.sh/actions/oidc \
1149 --client-id-list sts.amazonaws.com
1150 ```
1151
11522. Give the role a trust policy for your repository:
1153
1154 ```json
1155 {
1156 "Version": "2012-10-17",
1157 "Statement": [{
1158 "Effect": "Allow",
1159 "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/api.g1t.sh/actions/oidc" },
1160 "Action": "sts:AssumeRoleWithWebIdentity",
1161 "Condition": {
1162 "StringEquals": {
1163 "api.g1t.sh/actions/oidc:aud": "sts.amazonaws.com",
1164 "api.g1t.sh/actions/oidc:sub": "repo:acme/web:environment:production"
1165 }
1166 }
1167 }]
1168 }
1169 ```
1170
11713. Allow `sts.<region>.amazonaws.com`, and what the job deploys to, as
1172 [workflow-only domains](#reaching-your-cloud).
11734. Use `aws-actions/configure-aws-credentials@v4` with `role-to-assume`,
1174 as above.
1175
1176### Google Cloud
1177
11781. Make a workload identity pool and a provider for g1t:
1179
1180 ```sh
1181 gcloud iam workload-identity-pools create g1t --location=global
1182 gcloud iam workload-identity-pools providers create-oidc g1t \
1183 --location=global --workload-identity-pool=g1t \
1184 --issuer-uri=https://api.g1t.sh/actions/oidc \
1185 --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
1186 --attribute-condition="assertion.repository_owner == 'acme'"
1187 ```
1188
11892. Let the repository act as a service account:
1190
1191 ```sh
1192 gcloud iam service-accounts add-iam-policy-binding deploy@acme-prod.iam.gserviceaccount.com \
1193 --role=roles/iam.workloadIdentityUser \
1194 --member="principalSet://iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/g1t/attribute.repository/acme/web"
1195 ```
1196
11973. Allow `sts.googleapis.com` and `iamcredentials.googleapis.com`, and
1198 the APIs the job uses, as [workflow-only domains](#reaching-your-cloud).
11994. Use `google-github-actions/auth@v2` with
1200 `workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/g1t/providers/g1t`
1201 and `service_account`. It asks for the provider's own name as the
1202 audience, which the provider accepts unless you change its allowed
1203 audiences.
1204
1205### Azure
1206
12071. On the app registration or user-assigned managed identity, add a
1208 federated credential with the scenario **Other issuer**: issuer
1209 `https://api.g1t.sh/actions/oidc`, subject identifier
1210 `repo:acme/web:environment:production`, audience
1211 `api://AzureADTokenExchange`. Or:
1212
1213 ```sh
1214 az ad app federated-credential create --id <application id> --parameters '{
1215 "name": "g1t-acme-web-production",
1216 "issuer": "https://api.g1t.sh/actions/oidc",
1217 "subject": "repo:acme/web:environment:production",
1218 "audiences": ["api://AzureADTokenExchange"]
1219 }'
1220 ```
1221
12222. Give it a role on what it deploys to, as for any identity.
12233. Allow `login.microsoftonline.com` and `management.azure.com` as
1224 [workflow-only domains](#reaching-your-cloud).
12254. Use `azure/login@v2` with `client-id`, `tenant-id` and
1226 `subscription-id`, and no secret.
1227
1228A federated credential matches the subject exactly: add one per
1229environment or branch that deploys.
1230
1231### Cloudflare
1232
1233Cloudflare's API takes API tokens, not OIDC tokens. Keep a token scoped
1234to what the workflow deploys in a secret, available to that workflow only
1235(see [workflow-only domains](/guides/guardrails/#workflow-only-domains)
1236for limiting where it can be sent).
1237
1238A Worker of your own can trust g1t's jobs directly, by checking the token
1239a job sends it against g1t's keys:
1240
1241```ts
1242import { createRemoteJWKSet, jwtVerify } from "jose";
1243
1244const keys = createRemoteJWKSet(new URL("https://api.g1t.sh/actions/oidc/.well-known/jwks"));
1245
1246export async function fromG1tJob(request: Request): Promise<boolean> {
1247 const token = request.headers.get("authorization")?.replace(/^Bearer /, "") ?? "";
1248 const { payload } = await jwtVerify(token, keys, {
1249 issuer: "https://api.g1t.sh/actions/oidc",
1250 audience: "https://deploy.example.com",
1251 });
1252 return payload.sub === "repo:acme/web:environment:production";
1253}
1254```
1255
1256### npm
1257
1258npm's trusted publishing and provenance accept OIDC tokens only from the
1259CI services npm lists, and g1t is not one of them yet. Publish with a
1260granular access token in a secret instead:
1261
1262```yaml
1263- uses: actions/setup-node@v4
1264 with:
1265 node-version: 24
1266 registry-url: https://registry.npmjs.org
1267- run: npm publish
1268 env:
1269 NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
1270```
1271
1272See [What g1t can't do yet](/about/limitations/#no-npm-trusted-publishing-or-provenance).
1273
1274## Runs and logs
1275
1276Open a repository's **Actions** page, in its sidebar. Pick a workflow to
1277see its runs, run it by hand if it has `workflow_dispatch`, or turn it off
1278without touching its file.
1279
1280A run's page opens on its **Summary**. Its sidebar lists **All jobs**;
1281pick one to see its steps and their logs as they are written. Groups
1282fold, errors and warnings are marked, and secrets are replaced with
1283`***`. The page keeps itself up to date while the run goes on.
1284
1285### Live logs
1286
1287While a job runs, its log reaches the page within about two seconds of
1288being printed: the runner sends what a step prints every second, and the
1289page asks for more every second while a step is running.
1290
1291- The running step opens by itself, and its timer counts up. Its log
1292 follows the newest line while the end of it is on screen, with the last
1293 line wholly in view (above the tab bar on a phone). Scroll up and it
1294 stays where you are, with **Jump to latest** to go back to the end and
1295 follow it again.
1296- A step that prints nothing for a while says so under its last line:
1297 **Running · no output for 2m 10s**, or **no output yet** before its
1298 first line. The job's header names the step it is on.
1299- Colour that tools print is shown as they print it: bold, dim, italic,
1300 underline, the 16 terminal colours, 256 colours and 24-bit colour, for
1301 text and background, in both the light and the dark theme. Tools built
1302 on chalk or supports-color colour their output because the job has
1303 `GITHUB_ACTIONS=true`, as on GitHub; others need asking, such as
1304 `CARGO_TERM_COLOR: always` in the step's `env` for cargo. Other escape codes (moving the cursor, clearing a
1305 line) are left out, and a link keeps its text. Search reads the text
1306 without the colour codes.
1307- Every line is stamped with the time it was printed, as GitHub stamps
1308 it (`2026-10-12T09:41:07.1234567Z`). The clock button beside **Search
1309 logs** shows or hides the times; your browser remembers which.
1310- A download (the job's `log.txt` and the run's zip) has the time at the
1311 start of every line, as GitHub's downloaded logs do.
1312- Each line's number links to it, as on GitHub: `#step:3:12` is line 12
1313 of step 3 (0 is **Set up job**). Opening the link opens the step and
1314 any group the line is in, scrolls to the line and marks it.
1315- A long step's log stays fast. Each new piece of a running step's log is
1316 read once as it arrives, and a step of more than 2,000 lines draws only
1317 the lines on screen and a few around them. Search, groups, line links,
1318 following the newest line and **Jump to latest** work as they do on a
1319 short log. Your browser's own find (Ctrl+F) only sees the lines that are
1320 drawn, so use **Search logs** in a long log.
1321- The runner numbers each batch of the log it sends. When it sends a
1322 batch again because g1t's answer didn't reach it, g1t keeps the batch
1323 once, so a line never shows twice.
1324- If the page cannot reach g1t for a moment, it says **Reconnecting to the
1325 log** and tries again, waiting longer each time up to half a minute.
1326 A tab you are not looking at stops asking, and catches up when you come
1327 back to it.
1328
1329A cache restore, an artifact upload or download, and other transfers say
1330how far they have got every few seconds
1331(`Received 46137344 of 523190823 (8.8%), 43.9 MBs/sec`).
1332
1333A [self-hosted runner](/guides/self-hosted-runners/) stamps its lines and
1334writes these headers once it has updated itself to a release that does;
1335until then its logs show without times.
1336
1337### What a job's log says
1338
1339Each job's log reads as GitHub's does:
1340
1341| Step | What it shows |
1342| --- | --- |
1343| **Set up job** | The runner's version and name; the operating system; the runner image (`ImageOS` and `ImageVersion` when the machine's image sets them, and the software g1t's machines have); the machine (hosted or self-hosted, its architecture, CPUs and memory); what the job's [token](#the-jobs-token) may do, under **G1T_TOKEN Permissions**; the secret source; each action the job uses as it is downloaded, with the commit it resolved to (`Download action repository 'actions/setup-node@v4' (SHA:…)`); and the job's matrix. |
1344| A `run:` step | A folded group, `Run` and the script's first line, holding the whole script, the shell it ran in (`shell: /usr/bin/bash -e {0}`) and its variables under `env:`, secrets masked. Then what it printed. |
1345| A `uses:` step | A folded group, `Run owner/repo@ref`, holding its inputs under `with:` (an action's defaults among them) and its variables under `env:`. |
1346| **Post** steps and **Stop containers** | An action's `post`, saving the cache, then the job's containers stopped, as on GitHub. |
1347| **Complete job** | The job's outputs as they are set (`Set output 'version'`), and **Cleaning up orphan processes**: anything the steps started and left running is stopped (`Terminate orphan process: pid (1234) (node)`). The runner finds those processes by `RUNNER_TRACKING_ID`, as GitHub's does, so a process that unsets it is not found. Only on Linux. |
1348
1349### The run's summary
1350
1351The summary holds, top to bottom:
1352
1353| Part | What it shows |
1354| --- | --- |
1355| The run | What triggered it and when, who, the commit and its branch (or pull request), how the run stands, how long it took in all, and how many artifacts it kept. |
1356| The jobs graph | The workflow file and its event (`deploy.yml · on: push`), then every job and how they depend on each other. See [the jobs graph](#the-jobs-graph). |
1357| Annotations | Every job's errors, warnings and notices, counted by kind, each with the job it came from. Folds away. |
1358| Job summaries | What each job's steps wrote to `$GITHUB_STEP_SUMMARY`, under `<job> summary`. See [job summaries](#job-summaries). |
1359| Artifacts | What the run kept, to download. The count in the run's card jumps here. |
1360
1361Under **Run details** in the sidebar, **Workflow file** opens the
1362workflow as it was at the run's commit.
1363
1364### The jobs graph
1365
1366Jobs are laid out left to right by their `needs:`: a job that needs
1367nothing starts a row on the left, and each job sits one column to the
1368right of the furthest job it needs. Within a column, jobs are ordered so
1369that as few connectors as possible cross.
1370
1371| Node | How it shows |
1372| --- | --- |
1373| A job | Its status, its name and how long it took. Click it to open its steps and logs. |
1374| A job that deploys | Its environment's address (`environment.url`), as a link, under its name. |
1375| A matrix | One node with how many jobs it made (`test · 6 jobs`) and how they stand together. Click it to list each combination; click one to open it. |
1376| A reusable workflow | A box under the job that calls it, named with the called file, holding the called workflow's jobs laid out the same way; a matrix of calls shares one box, each call's jobs named with its combination. The jobs that need the calling job connect to the box. |
1377
1378Connectors run from a job to each job that needs it. They are red from a
1379job that failed, and move while the job they lead to is running (they
1380stay still if your system asks for reduced motion).
1381
1382A wide graph scrolls sideways inside its card, on a phone as on a desktop.
1383
1384The sidebar groups jobs the same way: a matrix's jobs, and a called
1385workflow's jobs, fold under their name.
1386
1387### Job summaries
1388
1389Markdown a step appends to the file in `$GITHUB_STEP_SUMMARY` shows on
1390the run's summary, below the graph, a card per job, in the order its
1391steps wrote it. The card's title opens the job:
1392
1393```yaml
1394- name: Report the tests
1395 if: always()
1396 run: |
1397 echo "### Test results" >> "$GITHUB_STEP_SUMMARY"
1398 echo "| Suite | Passed | Failed |" >> "$GITHUB_STEP_SUMMARY"
1399 echo "| --- | ---: | ---: |" >> "$GITHUB_STEP_SUMMARY"
1400 echo "| unit | 41 | 0 |" >> "$GITHUB_STEP_SUMMARY"
1401```
1402
1403| What | How it works |
1404| --- | --- |
1405| Formatting | GitHub-flavoured Markdown: tables, task lists, alerts such as `> [!WARNING]`, code blocks, and the HTML GitHub allows. Scripts, styles and event handlers are removed. |
1406| Secrets | Masked like the log, before the summary leaves the runner. |
1407| Size | Up to 1 MiB a step. A larger summary is refused with an error in the step's log, as on GitHub. |
1408| Steps | Up to 20 steps of a job keep a summary; later ones are dropped. |
1409| Actions | A JavaScript action's `core.summary` writes to the same file, so it works unchanged. |
1410
1411Summaries belong to their attempt: an earlier attempt keeps its own.
1412
1413### Re-running
1414
1415When a run has finished, someone with the Write role can run it again:
1416
1417| Button | Runs again |
1418| --- | --- |
1419| **Re-run all jobs** | Every job. |
1420| **Re-run failed jobs** | Jobs that did not succeed (failed, cancelled or skipped), and every job that needs one of them. |
1421| The re-run button beside a job's name | That job, and every job that needs it. A job of a matrix runs again with the rest of its matrix; a job of a reusable workflow runs again with the job that calls it. |
1422
1423Jobs that are not run again keep how they ended, and their outputs reach
1424the jobs that need them.
1425
1426Each re-run is a new **attempt**. The run keeps its number, `github.run_attempt`
1427goes up by one, and the attempt before is kept as it ended: its jobs,
1428their steps, logs and summaries. Pick one from **Attempt #** at the top of
1429the page to read it. Its jobs have ids of their own, so a link to an
1430earlier attempt's job keeps showing that job's log.
1431
1432#### Debug logging
1433
1434Each re-run asks whether to **Enable debug logging**. The new attempt's
1435jobs then run with:
1436
1437| Set | Effect |
1438| --- | --- |
1439| `RUNNER_DEBUG=1`, and `runner.debug` is `1` | Actions that check it, such as the toolkit's `core.isDebug()`, log more. |
1440| `ACTIONS_STEP_DEBUG=true` | `::debug::` lines are shown in the log. |
1441| `ACTIONS_RUNNER_DEBUG=true` | Set for actions that read it. |
1442
1443With debug logging, each step's log also says how its `if:` read:
1444`Evaluating condition for step`, the expression, and the result. Setting a
1445secret or variable named `ACTIONS_STEP_DEBUG` to `true` shows `::debug::`
1446lines on every run instead. The attempt picker marks attempts that ran
1447with debug logging.
1448
1449### Cancelling
1450
1451**Cancel run** is at the top of the run's page while any job of its latest
1452attempt has not finished: waiting for the jobs it needs, held for an
1453environment's approval, queued for a runner (a self-hosted job waits
1454until a runner with its labels takes it), or running. It is there on an
1455earlier attempt's page too, and on a job's, and the re-run buttons appear
1456only once the run has finished. A page left open on a finished run hears
1457when it is re-run, from anywhere, and offers Cancel run.
1458
1459It cancels jobs that have not started at once. A job that is running is
1460stopped the way GitHub stops one:
1461
14621. The step it is on gets `SIGINT`, then `SIGTERM` 7.5 seconds later, and
1463 is killed 2.5 seconds after that. Signals reach the processes the step
1464 started too; in a [job container](#job-containers) they reach only the
1465 `docker exec` that runs the step. The step ends **cancelled**.
14662. Its remaining steps run only if they ask to: `if: always()` or
1467 `if: cancelled()`. Steps without an `if:`, or with `success()` or
1468 `failure()`, are skipped.
14693. Post steps (an action's `post`, saving the cache) run, as their
1470 `post-if` is `always()` unless the action says otherwise.
14714. The job ends **cancelled**, whatever those steps came to.
1472
1473While that happens the run says **Cancelling**. A job still going 5
1474minutes after it was cancelled is stopped outright. **Force cancel**
1475(shown while a run is cancelling) stops every job at once, without
1476waiting for its cleanup steps.
1477
1478A step learns of a cancellation within about 10 seconds, even when it
1479prints nothing. A job on a [self-hosted runner](/guides/self-hosted-runners/)
1480is stopped the same way.
1481
1482### Searching and downloading logs
1483
1484The **Search logs** box above a job's steps shows only the lines that hold
1485what you type, in any case, with each match marked and every step that has
1486one opened. Lines inside folded groups are searched too.
1487
1488| Download | Where |
1489| --- | --- |
1490| One job's whole log, as text | The download button beside the job's name. |
1491| Every job's log of an attempt, as a zip | **Download logs** at the top of the run. The zip holds `1_<job>.txt` with each job's whole log, and a `<job>/` folder with `<step>_<step name>.txt` for each step. |
1492
1493A zip holds up to 24 MiB of logs; jobs past that are listed with a note
1494to download them on their own. Each job keeps up to 4 MB of log.
1495
1496### Status badges
1497
1498A badge shows how a workflow's latest finished run went: **passing**,
1499**failing**, **cancelled**, or **no status** before it has finished one.
1500
15011. Open the repository's **Actions** page and pick the workflow.
15022. Click **Create status badge**.
15033. Choose a branch and an event, if you want them, and copy the Markdown.
1504
1505```markdown
1506[![CI](https://g1t.sh/acme/web/actions/workflows/ci.yml/badge.svg)](https://g1t.sh/acme/web/actions?workflow=ci.yml)
1507```
1508
1509The address is `https://g1t.sh/{workspace}/{repo}/actions/workflows/{file}/badge.svg`,
1510where `{file}` is the workflow's file name in `.g1t/workflows/`. It takes:
1511
1512| Parameter | Shows |
1513| --- | --- |
1514| `branch` | Runs on that branch. Without it, the default branch's runs, or any branch's when the default branch has none. |
1515| `event` | Runs started by that event, such as `push` or `pull_request`. |
1516
1517A public repository's badge loads for anyone and is cached for a minute.
1518A private repository's loads only for someone who can see the repository,
1519so it does not show in a README read anywhere else.
1520
1521### Masking secrets
1522
1523Every secret's value is replaced with `***` wherever a job prints it, and
1524so is every value a step masks with `::add-mask::`. Each is masked in the
1525forms it shows up in:
1526
1527| Form | Example |
1528| --- | --- |
1529| As it is | `echo $API_KEY` |
1530| Each of its lines on its own | `cat key.pem`, which prints a private key a line at a time |
1531| Base64 | `echo -n $API_KEY \| base64`, or an `Authorization: Basic` header |
1532| JSON-escaped | A secret holding quotes or newlines printed inside JSON |
1533
1534Annotations' titles and messages, and step names, are masked the same way.
1535A job output that holds a secret in any of those forms is left out, with
1536a warning in the log, since outputs go to other jobs and to the run's page.
1537A value of one character is not masked: it would hide that character
1538everywhere.
1539
1540A [brokered secret](#brokered-secrets)'s stand-in is masked the same way.
1541Its real value never reaches the job, so the job cannot mask it; g1t
1542masks it in the log as the log arrives, should an answer have carried it
1543in.
1544
1545## Pull requests
1546
1547A pull request's workflows run on each new head: when it is opened, when
1548a commit is pushed to it, and, for one g1t makes, when g1t
1549marks it ready, which on g1t is when it first has code. Each head runs
1550each workflow once.
1551
1552They also start on the activity types `reopened` (also run by
1553default, as `opened` and `synchronize` are), `converted_to_draft`,
1554`ready_for_review`, `labeled`, `unlabeled`, `milestoned`, `demilestoned`,
1555`assigned`, `review_requested` and `closed`, and `edited` when the branch
1556a pull request merges into changes; `issue_comment` workflows on
1557`created`, `edited` (with `github.event.changes.body.from`) and `deleted`; `issues` workflows on `assigned`, `labeled`, `unlabeled`, `milestoned` and
1558`demilestoned` too. List them under `types:` to run on them. For
1559`labeled` and `unlabeled`, `github.event.label` names the label; for
1560`assigned`, `github.event.assignee` names who was assigned. Assigning
1561several people at once starts one run, which names the first of them. A pull
1562request's `branches` filter, `github.base_ref` and
1563`pull_request.base.ref` are the branch it merges into, which is not
1564always the default branch: see
1565[pull requests into other branches](/guides/base-branches/).
1566
1567### What a pull request's run builds
1568
1569As on GitHub, a `pull_request` run builds the pull request merged into
1570its base branch, not its head alone:
1571
1572| | Is |
1573| --- | --- |
1574| `github.ref`, `GITHUB_REF` | `refs/pull/<number>/merge` |
1575| `github.sha`, `GITHUB_SHA` | The test merge commit at `refs/pull/<number>/merge`: the head merged into the base branch's latest commit, its parents the base's commit and then the head, with the message `Merge <head> into <base>` |
1576| `github.event.pull_request.head.sha` | The head commit |
1577| `github.event.pull_request.merge_commit_sha` | The test merge commit |
1578| `actions/checkout` with no `ref` | The test merge commit, fetched from the repository itself as `+<sha>:refs/remotes/pull/<number>/merge` |
1579
1580The workflow files the run uses are read from the test merge commit too.
1581Its status, its check runs and the run itself stay on the pull request's
1582head, so required checks hold the merge as before. `pull_request_review`
1583runs build the same commit. `pull_request_target` runs are unchanged: they
1584build the base branch, never the pull request's code.
1585
1586g1t keeps two refs in the repository for every open pull request, from a
1587branch, a fork or a pull request g1t made: `refs/pull/<number>/head` at
1588its head (a fork's head commit is copied into the repository), and
1589`refs/pull/<number>/merge` at its test merge commit. Both move each time
1590the head is pushed to, the pull request is opened or reopened, or its
1591base branch changes, and both can be fetched like any branch:
1592
1593```sh
1594git fetch origin refs/pull/12/head refs/pull/12/merge
1595```
1596
1597The merge commit moves when the head does. When only the base branch
1598moves, it moves at the pull request's next event, as on GitHub, where
1599workflows do not run again because the base branch moved.
1600
1601**A pull request with conflicts runs no `pull_request` workflows**, as on
1602GitHub: it has no merge commit to build. Its checks box says so, and the
1603runs start once the conflicts are resolved and the head is pushed again.
1604g1t merges the two the way git does: a file only one side changed is
1605taken from it, and changes both made to the same file merge unless they
1606change the same or neighbouring lines differently, or the file is binary.
1607When g1t cannot make the merge itself (a file one side renamed or deleted
1608while the other changed it, a file where the other side has a directory,
1609more than 300 changed files, or a file over 1 MB that both changed), the
1610run builds the head commit instead, as it did before.
1611
1612A pull request from outside the workspace may wait for approval before
1613its workflows run: see [pull requests from outside](#pull-requests-from-outside).
1614
1615`github.event.pull_request` reads as it does on GitHub. For a pull request
1616g1t made, `pull_request.user` is g1t (`login` `g1t`, `type` `Bot`), and
1617`pull_request.requested_by` names the person who asked for it; it is `null`
1618on anyone else's. `github.event.issue.requested_by` does the same for an
1619issue g1t's agent filed. `sender` is whoever caused the event.
1620
1621## The event payload
1622
1623`github.event`, and the file at `GITHUB_EVENT_PATH`, hold the webhook
1624payload GitHub would send for the event, with the same keys and types.
1625
1626- **Ids are numbers.** `repository.id`, `pull_request.id`, `issue.id`,
1627 `review.id`, `sender.id`, every `user.id` and `owner.id` are whole
1628 numbers, the same for an object wherever it appears and below 2^53, so
1629 JavaScript reads them exactly. `node_id` is g1t's own id, such as
1630 `rep_01kpw0…`. A person's or workspace's number comes from their name,
1631 so it changes when they are renamed. `github.repository_id`,
1632 `github.repository_owner_id`, `github.actor_id` and their
1633 `GITHUB_REPOSITORY_ID`, `GITHUB_REPOSITORY_OWNER_ID` and
1634 `GITHUB_ACTOR_ID` variables are the same numbers, as text.
1635- **What the API takes by id keeps g1t's id.** `comment.id`,
1636 `release.id`, `deployment.id`, `deployment_status.id`,
1637 `workflow_run.id` and `workflow_run.workflow_id` are g1t's ids
1638 (`cmt_…`, `rel_…`, `run_…`, `wfl_…`), so you can pass them straight
1639 back, as in `PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}`
1640 or `run-id: ${{ github.event.workflow_run.id }}`.
1641- **`repository.owner.type`** is `User` for a repository in a person's
1642 own workspace and `Organization` for any other workspace's.
1643- **On `push`,** `created` is true for a new branch or tag, and `forced`
1644 is true when the commit before the push is not in the new commit's
1645 history. g1t reads the 20 latest commits to tell, so a forced push onto
1646 a longer history says `false`. `head_commit` is the commit pushed and
1647 `commits` lists at most 20, oldest first. `pusher.email` is the
1648 pusher's noreply address (`<id>+<username>@users.noreply.g1t.sh`), or
1649 `g1t@users.noreply.g1t.sh` for g1t.
1650- **A commit's `added`, `removed` and `modified`** list the paths the
1651 push changed when it pushed one commit and a workflow on `push` has a
1652 `paths` or `paths-ignore` filter, which makes g1t compare the push.
1653 Otherwise, and for a push of several commits, they are empty lists.
1654- **On pull request events,** `pull_request.base.sha` is the tip of the
1655 branch it merges into when the event happened, and
1656 `pull_request.merge_commit_sha` is set only once it is merged, to the
1657 commit it landed as.
1658- **On `workflow_run`,** `workflow_run` has the followed run's `name`,
1659 `path`, `event`, `status`, `conclusion`, `head_branch`, `head_sha`,
1660 `head_commit`, `run_number`, `run_attempt`, `actor`, `created_at`,
1661 `updated_at`, `html_url`, `repository` and `pull_requests`. Each pull
1662 request there has its `id`, `number`, `url`, and `head` and `base`
1663 with their `ref`, `sha` and `repo` (`id`, `url`, `name`). As on
1664 GitHub, a pull request from a fork is not listed.
1665
1666Labels on `issue`, `pull_request` and a `labeled` event's `label` have
1667their `id`, `name`, `color` and `description` as the repository has
1668them, and `default`, which is true for the labels a repository starts
1669with.
1670
1671## Checks
1672
1673A pull request's checks are its workflows. Each workflow that runs on
1674`pull_request` runs for every pull request, whoever opened it, a
1675person or an agent, and its runs report a check named after the workflow:
1676a workflow with `name: CI` reports `CI`, with the status context
1677`CI / pull_request` (the workflow's name and the event). Each of its jobs
1678is a [check run](/guides/checks/) on the commit, shown as
1679`CI / test (pull_request)` beside it wherever it appears.
1680
1681- **Which checks a merge needs** is up to the [rules](/guides/rules/) of the branch it merges into,
1682 their [required status checks](/guides/pull-requests/#required-status-checks),
1683 under **Settings → Rules**. A required check that failed,
1684 is still running or has not reported holds the merge. Checks that are not
1685 required are shown on the pull request and never hold it.
1686- **In a repository that merges through the [merge queue](/guides/merge-queue/)**,
1687 workflows with `on: merge_group` run on each combined state the queue
1688 builds, on the branch `g1t-queue/<entry>`, and the state lands only if
1689 they and every required check pass on it. A workflow behind a required
1690 check needs `merge_group` in its `on:`.
1691- **A pull request g1t is working on** goes back to g1t when
1692 a check fails, with the end of each failed job's log. The agent reads the
1693 run and its logs with the same tools you have, fixes the cause, and
1694 pushes; the workflows run again. See
1695 [seeing it through](/guides/working-with-g1t/#seeing-it-through).
1696
1697```yaml
1698name: CI
1699
1700on:
1701 pull_request:
1702 push:
1703 branches: [main]
1704 merge_group:
1705```
1706
1707### Add CI
1708
1709A repository with no workflows has nothing that proves a change works, for
1710people or for agents. Its pull requests, its **Branches and merging**
1711settings and its **Actions** page say **This repository has no checks**,
1712with an **Add CI** button. Anyone who can push to the repository can use it:
1713
17141. Choose **Add CI**. g1t looks at the files at the repository's root and
1715 writes a starter workflow with a job for each stack it finds, up to
1716 three: Node (npm, pnpm, Yarn or Bun), Rust, Go, Python (pip or uv), Ruby,
1717 Java (Maven or Gradle), .NET, or Make. Each job installs, lints where
1718 the project says how, builds and tests. When it finds none, the job is a
1719 placeholder that fails until you replace its last step with your own
1720 commands.
17212. The workflow is committed as `.g1t/workflows/ci.yml` on a new branch,
1722 `add-ci`, and opened as a pull request, by you. It is named `CI` and runs
1723 on `pull_request`, on `push` to the default branch, and on `merge_group`.
17243. Change it on the pull request if the steps are not how your project
1725 builds, and merge it.
17264. Once it has run, `CI` is offered under **Require status checks to pass
1727 before merging**.
1728 Require it, so that nothing merges into the default branch unless it
1729 passes.
1730
1731## Secrets and variables
1732
1733Secrets are read as `${{ secrets.KEY }}` and config as `${{ vars.KEY }}`,
1734from the rows under **Settings → Secrets and variables** that are
1735available to Workflows. A job with `environment: production` reads each
1736key's Production row; other jobs read the rows for all environments. A
1737job with an `environment:` also makes a deployment to it; see
1738[deployments from g1t Actions](/guides/deployments-api/#deployments-from-g1t-actions). See
1739[Secrets and variables](/guides/secrets-and-variables/) for how rows,
1740environments and the workspace's rows work.
1741
1742To deploy to AWS, Google Cloud or Azure, or to a server of your own,
1743you need no secret at all: give the job `permissions: id-token: write`
1744and trade its [OIDC token](#oidc-tokens) for short-lived credentials.
1745
1746Every job also gets `${{ secrets.G1T_TOKEN }}`, [its own token](#the-jobs-token),
1747with `GITHUB_TOKEN` as its alias. A pull request's runs get secrets only
1748when its author has the Write [role](/guides/access-and-roles/) or higher
1749on the repository, a member or an outside collaborator, or is g1t working
1750on its own. For a pull request g1t made, its author is g1t and the person
1751who asked for it is the one whose role counts. Anyone else's, such as one
1752from a fork or by someone with Read or Triage, runs without secrets and
1753with a token that can only read. See
1754[who gets secrets](/guides/secrets-and-variables/#who-gets-secrets).
1755
1756## The job's token
1757
1758Each job gets a token of its own, `${{ secrets.G1T_TOKEN }}`
1759(`${{ secrets.GITHUB_TOKEN }}` and `${{ github.token }}` are the same).
1760`actions/checkout` uses it, and so can any step that calls the
1761[API](/reference/api/) or pushes with git:
1762
1763- It reaches **this repository only**. Every other repository, even one
1764 in the same workspace, is refused. So are packages: it reaches this
1765 repository's own, and another package only once the package's admins
1766 add this repository under its
1767 [Manage Actions access](/guides/packages/#manage-actions-access).
1768- It can do **what its `permissions:` say**, and nothing more.
1769- It **stops working when the job ends**, however it ends.
1770- Everything it changes is in the [audit log](/guides/audit-log/) as that
1771 job's, under its run.
1772- What it changes **starts no workflows**: a push, a pull request, an issue
1773 or a comment made with it runs nothing, so a workflow cannot set itself
1774 off. `workflow_dispatch` and [`repository_dispatch`](#repository-dispatch)
1775 are the exceptions, for a workflow that means to start another.
1776- It **never puts g1t to work**. A comment it posts that mentions
1777 `@g1t` starts nothing, and it cannot assign an issue or a plan to g1t,
1778 queue one for it, hand it work or ask it for a review. Otherwise a
1779 workflow that asks g1t to fix a failing check would run again on g1t's
1780 push, and ask again, without end. A step that should put g1t to work
1781 uses a token of a person's own, stored as a
1782 [secret](/guides/secrets-and-variables/).
1783
1784`permissions:` goes at the top of the workflow, for every job, or on a job,
1785which then ignores the workflow's. Once either is written, every permission
1786it leaves out is `none`:
1787
1788```yaml
1789permissions:
1790 contents: read
1791
1792jobs:
1793 release:
1794 runs-on: ubuntu-latest
1795 permissions:
1796 contents: write
1797 pull-requests: write
1798 steps:
1799 - uses: actions/checkout@v5
1800 - run: ./scripts/release.sh
1801```
1802
1803| Permission | `read` lets it | `write` also lets it |
1804| --- | --- | --- |
1805| `contents` | Clone and fetch with git, read the repository | Push, publish releases |
1806| `pull-requests` | Read pull requests | Open, review, close and merge them |
1807| `issues` | Read issues | Open, edit, comment on and close them |
1808| `actions` | Read workflows, runs and logs | Run, cancel and re-run them |
1809| `checks`, `statuses` | Read statuses and check runs | Report them |
1810| `deployments`, `pages` | Read deployments | Report them |
1811| `packages` | Pull packages | Push and publish them |
1812| `security-events` | Read security alerts | Upload code scanning results, change alerts |
1813| `metadata` | Always `read` | |
1814| `id-token` | Nothing | Ask for an [OIDC token](#oidc-tokens) |
1815| `discussions`, `attestations`, `models`, `repository-projects` | Nothing on g1t | Nothing on g1t |
1816
1817`read-all` and `write-all` set every permission; `permissions: {}` sets
1818none, so the token cannot even clone a private repository. A reusable
1819workflow's jobs get no more than the job that calls it.
1820
1821There is no `workflows` permission for a job's token: it can never add,
1822change or delete a file under `.g1t/workflows/` or `.github/workflows/`,
1823even with `contents: write`. A push that does is declined, naming the file,
1824so a workflow cannot rewrite the workflows that run with its repository's
1825secrets. To change workflows from a job, push with a
1826[personal access token](/guides/authentication/#workflow-files) that has
1827Workflow files: write, kept as a secret.
1828
1829The job's token is the repository's workspace acting with the Write role
1830at most, never Admin: it cannot manage webhooks, secrets, deploy keys or who
1831has access, whatever it asks for.
1832
1833**Without `permissions:`** a job gets the repository's default, which
1834someone with the Admin role sets under **Settings → Actions**:
1835
1836| Repository | Default until someone chooses |
1837| --- | --- |
1838| Made before restricted tokens came in, in October 2026 | **Read and write**: every permission at `write`, as before |
1839| Made since | The workspace's default for new repositories: **Read repository contents and packages** (`contents: read`, `packages: read`) unless an owner chose otherwise |
1840
1841The workspace's owners set that default, and a **maximum**, under the
1842workspace's **Settings → Actions**: with a maximum of **Read only**, no
1843repository's default goes past `contents: read` and `packages: read`,
1844whatever it chose. Workflows that write `permissions:` get what they
1845write either way, and whatever a workflow asks for, a pull request from
1846outside the repository's writers (a fork, or someone with Read or Triage)
1847gets a token that can only read.
1848
1849**Allow g1t Actions to create and approve pull requests** is off unless a
1850repository's admin turns it on under **Settings → Actions**, and they can
1851only where the workspace's owners allow it. Until then a job's token
1852cannot open a pull request or approve one, whatever its `pull-requests`
1853permission says; it can still read, comment on, review with changes
1854requested, and merge them.
1855
1856The token can never change secrets, variables, environments' rules or
1857the Actions settings, approve runs or deployments, or reach another
1858repository.
1859
1860## Environments
1861
1862A job that names an environment with `environment:` reads that
1863environment's [secrets and variables](/guides/secrets-and-variables/#a-value-per-environment)
1864and records a [deployment](/guides/deployments-api/#deployments-from-g1t-actions)
1865to it. Give the environment protection rules, and such a job waits until
1866they let it through, and only then gets the environment's secrets:
1867
1868| Rule | What it does |
1869| --- | --- |
1870| **Required reviewers** | Up to 6 people or teams. The job waits until one of them approves it. |
1871| **Prevent self-review** | Whoever started the run cannot approve it, even as a reviewer. |
1872| **Wait timer** | Minutes the job waits once it reaches the environment, up to 43,200 (30 days). |
1873| **Deployment branches and tags** | **All branches**; **Protected branches only**, those the repository's [rules](/guides/rules/) protect, the default branch included; or **Selected branches and tags**, by pattern, such as `main`, `release/*` or `v*`. A job on any other ref fails, saying so. A pull request's run is on no branch, so it can deploy only where all branches may. |
1874| **Allow admins to bypass** | On unless you turn it off: someone with the Admin role may approve without being a reviewer, which also skips the wait timer. |
1875
1876To set them:
1877
18781. Open the repository's **Settings → Environments**. It lists every
1879 environment your workflows, secrets and deployments name.
18802. Choose one, or name a new one, and set its rules.
18813. Save. Runs that reach the environment from then on wait by them.
1882
1883A run whose jobs wait shows **Waiting for review** at the top of its page,
1884with the environments, the jobs each holds, its reviewers and when its
1885wait timer runs out. Reviewers are told in their [notifications](/guides/notifications/);
1886on the run's page they choose **Approve and deploy** or **Reject**, with
1887room for a comment. One review covers every job of the run that names the
1888environment. A rejected job fails, and so does anything that needs it. The
1889rest of the run goes on meanwhile: jobs that do not need the waiting ones
1890run.
1891
1892The environment's name may be an expression, such as
1893`environment: ${{ inputs.target }}`: it is read once the job's needs are
1894done, and its rules and secrets are that environment's. Names are matched
1895without regard to case.
1896
1897From the API, `PUT /repos/{owner}/{repo}/environments/{environment}` sets
1898the rules, with `reviewers`, `prevent_self_review`, `wait_timer`,
1899`deployment_branch_policy`, `branch_policies` and `can_admins_bypass`;
1900`GET` on the same route returns them as `protection_rules`; `DELETE`
1901removes them. `POST /repos/{owner}/{repo}/actions/runs/{id}/pending_deployments`
1902approves or rejects a run's waiting jobs:
1903
1904```sh
1905curl -X PUT https://api.g1t.sh/repos/acme/web/environments/production \
1906 -H "Authorization: Bearer $G1T_TOKEN" -H "Content-Type: application/json" \
1907 -d '{"reviewers": [{"type": "Team", "name": "deployers"}], "wait_timer": 10,
1908 "deployment_branch_policy": {"protected_branches": true, "custom_branch_policies": false}}'
1909```
1910
1911A job's own token cannot approve, reject or change any of it.
1912
1913## Pull requests from outside
1914
1915A pull request from someone outside the workspace runs code anyone could
1916have written. By the repository's **approval policy**, its runs wait as
1917**Approval required** until someone with the Write
1918[role](/guides/access-and-roles/) chooses **Approve and run** on the run's
1919page. Nothing runs before then: no job starts, and no token or secret is
1920handed out.
1921
1922| Policy, under **Settings → Actions** | Whose pull requests' runs wait |
1923| --- | --- |
1924| **First-time contributors** | Someone outside the workspace who has not had a pull request merged here yet. |
1925| **Outside contributors** (the default) | Those, and everyone outside the workspace who cannot push here: pull requests from forks, and from people with Read or Triage. |
1926| **All external contributors** | Everyone outside the workspace, [outside collaborators](/guides/access-and-roles/#outside-collaborators) with Write included. |
1927
1928Members' pull requests never wait, nor do pull requests g1t opens on its
1929own. For a pull request g1t made for someone, that person is the one whose
1930policy counts. Each new push to the pull request waits again.
1931`pull_request_target` runs, which run the default branch's workflow and
1932code, never wait.
1933
1934From the API: `POST /repos/{owner}/{repo}/actions/runs/{id}/approve`
1935approves a run, and `GET` and `PUT
1936/repos/{owner}/{repo}/actions/permissions/fork-pr-contributor-approval`
1937read and set the policy, as `approval_policy`.
1938
1939## Repository dispatch
1940
1941`POST /repos/{owner}/{repo}/dispatches` starts the default branch's
1942workflows that run `on: repository_dispatch` for its `event_type`, those
1943listing it under `types:` or listing none. `client_payload` is theirs to
1944read as `github.event.client_payload`:
1945
1946```yaml
1947on:
1948 repository_dispatch:
1949 types: [docs-published]
1950
1951jobs:
1952 announce:
1953 runs-on: ubuntu-latest
1954 steps:
1955 - run: echo "Docs ${{ github.event.client_payload.version }} are out"
1956```
1957
1958```sh
1959curl -X POST https://api.g1t.sh/repos/acme/web/dispatches \
1960 -H "Authorization: Bearer $G1T_TOKEN" -H "Content-Type: application/json" \
1961 -d '{"event_type": "docs-published", "client_payload": {"version": "2.4.0"}}'
1962```
1963
1964It needs the Write role, or a token with `code:write`; a job's own token
1965needs `contents: write`. `client_payload` is a JSON object of at most 10
1966properties and 64 KB.
1967
1968## Who may run workflows
1969
1970What you can do with a repository's workflows follows your
1971[role](/guides/access-and-roles/) on it:
1972
1973| | Needs |
1974| --- | --- |
1975| See workflows, runs and their logs | Read: on a public repository, anyone |
1976| Run a workflow by hand, cancel or re-run a run, approve a pull request's run from outside | Write |
1977| Approve or reject a job waiting for an environment | One of the environment's reviewers |
1978| Enable or disable a workflow | Maintain |
1979| The repository's secrets and variables, seeing them included; environments' rules; **Settings → Actions** | Admin |
1980
1981Jobs run in g1t's sandboxes, so they need the
1982[g1t plan](/guides/usage-and-billing/#the-g1t-plan) or
1983[the trial](/guides/usage-and-billing/#the-trial); jobs on
1984[self-hosted runners](/guides/self-hosted-runners/#billing) need neither.
1985On a public repository,
1986[g1t's open-source pool](/guides/usage-and-billing/#the-open-source-pool)
1987runs them too, after a card check, until the month's pool is spent.
1988
1989Before each job starts, g1t reserves what it may cost (its time limit at
1990the sandbox price) with billing, and settles what it really cost when it
1991ends; each job's sandbox is charged as
1992[sandbox time](/guides/usage-and-billing/#sandbox-time), from the first
1993second. A job billing refuses does not start: it is recorded as failed
1994with "Not started:" and the reason, such as "Workflows run in g1t's
1995sandboxes, which cost real money, so they need the g1t plan ($20 a month)
1996or a card check", and what to do about it. The
1997Actions page tells people with Write on a repository whose workspace
1998cannot run jobs before the first run.
1999
2000## From the API
2001
2002The routes follow the standard Actions REST shape, so existing scripts
2003usually work once they point at `https://api.g1t.sh`.
2004
2005| `workflow` action | Route |
2006| --- | --- |
2007| `list` | `GET /repos/{owner}/{repo}/actions/workflows` |
2008| `list_runs` | `GET /repos/{owner}/{repo}/actions/runs`, with `workflow`, `branch`, `event`, `pull`, `head_sha` |
2009| `get_run` | `GET /repos/{owner}/{repo}/actions/runs/{id}` |
2010| `job_logs` | `GET /repos/{owner}/{repo}/actions/jobs/{job}/logs?after=`, or `?format=text` for the whole log as plain text |
2011| `get_run` with `attempt` | `GET /repos/{owner}/{repo}/actions/runs/{id}/attempts/{attempt}` |
2012| No tool: a download | `GET /repos/{owner}/{repo}/actions/runs/{id}/logs`, or `…/attempts/{attempt}/logs`: every job's log as a zip |
2013| `dispatch` | `POST /repos/{owner}/{repo}/actions/workflows/{workflow}/dispatches` with `ref` and `inputs` |
2014| `cancel` | `POST /repos/{owner}/{repo}/actions/runs/{id}/cancel`; `…/force-cancel`, or `force`, to stop running jobs without their cleanup steps |
2015| `rerun` | `POST …/runs/{id}/rerun`, or `…/rerun-failed-jobs`; one job and those that need it with `POST /repos/{owner}/{repo}/actions/jobs/{job}/rerun`. Each takes `enable_debug_logging` (or `debug`) |
2016| `update` | `PUT …/workflows/{workflow}/enable` and `…/disable` |
2017| `approve_run` | `POST /repos/{owner}/{repo}/actions/runs/{id}/approve` |
2018| `pending_deployments` | `GET /repos/{owner}/{repo}/actions/runs/{id}/pending_deployments` |
2019| `review_deployments` | `POST /repos/{owner}/{repo}/actions/runs/{id}/pending_deployments` with `environment_names`, `state` and `comment` |
2020| `get_environment` | `GET /repos/{owner}/{repo}/environments/{environment}` |
2021| `update_environment` | `PUT /repos/{owner}/{repo}/environments/{environment}` |
2022| `delete_environment` | `DELETE /repos/{owner}/{repo}/environments/{environment}` |
2023| `get_permissions`, `set_permissions` | `GET` and `PUT /repos/{owner}/{repo}/actions/permissions/workflow`, with `default_workflow_permissions` (`read`, `write` or `inherit`) and `can_approve_pull_request_reviews` |
2024| `get_workspace_permissions`, `set_workspace_permissions` | `GET` and `PUT /workspaces/{workspace}/actions/permissions/workflow`, with `default_workflow_permissions`, `max_workflow_permissions` and `can_approve_pull_request_reviews` |
2025| `get_approval_policy`, `set_approval_policy` | `GET` and `PUT /repos/{owner}/{repo}/actions/permissions/fork-pr-contributor-approval` |
2026| `get_access`, `set_access` | `GET` and `PUT /repos/{owner}/{repo}/actions/permissions/access`, with `access_level` (`none` or `organization`) |
2027| `repository_dispatch` | `POST /repos/{owner}/{repo}/dispatches` with `event_type` and `client_payload` |
2028| `list_artifacts` | `GET /repos/{owner}/{repo}/actions/artifacts`, with `name`, `page`, `per_page` |
2029| `run_artifacts` | `GET …/actions/runs/{id}/artifacts`, with `name` |
2030| `get_artifact` | `GET …/actions/artifacts/{artifact_id}` |
2031| `download_artifact` | `GET …/actions/artifacts/{artifact_id}/zip`: a `302` to a link good for 10 minutes |
2032| `delete_artifact` | `DELETE …/actions/artifacts/{artifact_id}` |
2033| `artifact_retention`, `set_artifact_retention` | `GET` and `PUT …/actions/permissions/artifact-and-log-retention` with `days` (it sets artifacts' days only; logs are kept with their run) |
2034
2035Secrets and variables have a tool of their own, `secret`:
2036
2037| `secret` action | Route |
2038| --- | --- |
2039| `list_secrets`, `set_secret`, `delete_secret` | `GET /repos/{owner}/{repo}/actions/secrets`, `PUT` and `DELETE …/secrets/{name}` |
2040| `list_variables`, `set_variable`, `delete_variable` | `GET` and `POST /repos/{owner}/{repo}/actions/variables`, `PATCH` and `DELETE …/variables/{name}` |
2041
2042Workspace secrets and variables are under
2043`/workspaces/{workspace}/actions/secrets` and `…/variables`. The fields
2044g1t adds (environments, who reads a row, linked repositories) are in
2045[Secrets and variables](/guides/secrets-and-variables/#from-the-api).
2046
2047```sh
2048curl -X POST https://api.g1t.sh/repos/acme/web/actions/workflows/ci.yml/dispatches \
2049 -H "Authorization: Bearer $G1T_TOKEN" -H "Content-Type: application/json" \
2050 -d '{"ref": "main", "inputs": {"environment": "staging"}}'
2051```
2052
2053Give only the inputs the workflow declares under
2054`on.workflow_dispatch.inputs`: a run with any other input is refused, and
2055the answer names the inputs it did not expect. A `boolean` input is `true`
2056or `false`, a `number` a number, a `choice` one of its `options`, and an
2057`environment` the name of one of the repository's
2058[environments](#environments). In the run, the `inputs` context keeps
2059booleans and numbers as they are, and `github.event.inputs` has every
2060input as text, `"true"` and `"3"`, as on GitHub.
2061
2062To save an artifact from a script, follow the redirect:
2063
2064```sh
2065curl -L -o web-dist.zip -H "Authorization: Bearer $G1T_TOKEN" \
2066 https://api.g1t.sh/repos/acme/web/actions/artifacts/4182/zip
2067```