Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

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